Problem
The repo statically analyzes every language surface except shell:
| Surface |
Linter |
In CI / hooks? |
| TypeScript |
eslint |
✅ |
| Python (agent) |
semgrep + agent quality |
✅ |
| GitHub Actions |
zizmor |
✅ |
| Secrets / deps |
gitleaks / osv-scanner / grype / retire |
✅ |
| Shell scripts |
— none — |
❌ |
grep shellcheck across mise.toml, .github/, and all package mise.toml files returns nothing. Yet there are 5 first-party shell scripts in-tree today:
Shell is therefore a maintained — and partly shipped and destructive — surface with zero static analysis. The cleanup script in PR #109 made this gap concrete: its review relied on a shellcheck binary fetched ad hoc via mise x, not on any committed project gate, so the next shell change has nothing protecting it.
Why now
A defense-in-depth shell linter catches the exact class of bug that bit the cleanup script before hardening — fail-open logic, unquoted expansions, set -e foot-guns (SC2086, SC2046, SC2164, etc.) — none of which semgrep's current rule set flags for bash.
Proposed change
- Add
shellcheck to the root mise.toml [tools] block (pin a version, consistent with gitleaks/semgrep/zizmor).
- Add a
security:shell (or lint:shell) mise task that runs shellcheck over first-party scripts, excluding generated trees (cdk/cdk.out/**, node_modules/**).
- Wire it into the
security aggregate task and the prek pre-commit/pre-push hooks (matching how the other linters are wired).
- Add it to the CI workflow alongside the existing security/lint steps.
Scoping note (already measured)
Running shellcheck against the existing scripts today:
- 0 error-level findings across all 5 scripts.
- Only 1 info-level finding (SC2016 in
agent/run.sh, a JMESPath/literal-backtick false positive — same pattern already annotated with a line-scoped # shellcheck disable=SC2016 in cleanup-ephemeral-stacks.sh).
So a gate at --severity error (or warning) lands without a backlog of pre-existing failures — low-risk to introduce.
Acceptance criteria
References
Problem
The repo statically analyzes every language surface except shell:
grep shellcheckacrossmise.toml,.github/, and all packagemise.tomlfiles returns nothing. Yet there are 5 first-party shell scripts in-tree today:agent/run.sh— ships inside the deployed container imageagent/prepare-commit-msg.sh— ships inside the deployed container imageagent/scripts/create-local-tables.shscripts/ci-build.sh— runs the full monorepo build (mirrors CI)scripts/cleanup-ephemeral-stacks.sh— destructive (deletes CloudFormation stacks + ENIs); added in PR feat(ops): automated ephemeral stack cleanup script #109 (feat: automated ephemeral stack cleanup (scheduled) #72)Shell is therefore a maintained — and partly shipped and destructive — surface with zero static analysis. The cleanup script in PR #109 made this gap concrete: its review relied on a
shellcheckbinary fetched ad hoc viamise x, not on any committed project gate, so the next shell change has nothing protecting it.Why now
A defense-in-depth shell linter catches the exact class of bug that bit the cleanup script before hardening — fail-open logic, unquoted expansions,
set -efoot-guns (SC2086, SC2046, SC2164, etc.) — none of which semgrep's current rule set flags for bash.Proposed change
shellcheckto the rootmise.toml[tools]block (pin a version, consistent withgitleaks/semgrep/zizmor).security:shell(orlint:shell) mise task that runs shellcheck over first-party scripts, excluding generated trees (cdk/cdk.out/**,node_modules/**).securityaggregate task and the prek pre-commit/pre-push hooks (matching how the other linters are wired).Scoping note (already measured)
Running shellcheck against the existing scripts today:
agent/run.sh, a JMESPath/literal-backtick false positive — same pattern already annotated with a line-scoped# shellcheck disable=SC2016incleanup-ephemeral-stacks.sh).So a gate at
--severity error(orwarning) lands without a backlog of pre-existing failures — low-risk to introduce.Acceptance criteria
shellcheckpinned in rootmise.toml[tools]*.sh, excludes generated/vendored treessecurityaggregate and to prek pre-commit + pre-push hookserror, orwarningif the team wants stricter)warning/infois chosen)References
security:gh-actions(zizmor),security:sast(semgrep) in rootmise.toml