KB-7152

03 — Shell / injection review (A1, A2, A7, A8, A15)

4 min read Revision 1
c1-stagingclaude-r3-self-gateinjectionguard-self-tests

03 — Shell / injection review (rows 1-2, 28-29; attacks A1, A2, A7, A8, A15)

Static greps (authoritative copies on VPS)

grep -RInE 'eval|sh -c|sh -lc|bash -c|\$[*]' bin plan   → only 3 hits, all COMMENT lines in _common.sh
grep -RInE 'docker exec.*sh -c|.*sh -lc' bin plan       → only 1 COMMENT line
secrets grep (SYNC_SECRET|PASSWORD|SECRET|TOKEN|key…)    → NO_SECRETS_FOUND
'directus' in code (bin/plan/sql)                        → 1 hit only: _common.sh:25 off-limits guard list
official table tokens in sql/                            → only p5:48 in-sandbox isolation probe + p6:66 error string
APR/quorum/promotion path grep                           → NO_APR_QUORUM_PROMOTION_PATH

psql is always invoked as explicit argv: _common.sh:87 docker exec postgres psql -U "$(stg_pg_user)" -v ON_ERROR_STOP=1 "$@" -d "$db" -f "$rmt" </dev/null. No sh -lc, no eval, no $*. User values (purpose/owner/ttl) ride as psql -v argv words; SQL files use psql :'var' / :"var" (psql-side escaping). The sandbox id is regex-gated (^c1_staging_[0-9]{8}_[0-9]{4}$) BEFORE it is ever interpolated into any SQL string.

Static parse

bash -n → 8/8 bins + plan OK. shellcheck -S warning (v0.9.0) → CLEAN for bin/* + plan.

Live hostile guard self-tests (no-write; each fails closed BEFORE any DB write)

Every case below stg_dies before the first DB contact (the two "absent" cases issue a single read-only SELECT). Ran on VPS; post-fix re-run identical:

case input exit meaning
A1_create_rm_rf --sandbox-id 'c1_staging_x; rm -rf /' 4 regex reject (A1 refuted)
A2_create_sqlinj --sandbox-id "x'; DROP DATABASE directus;--" 4 regex reject (A2 refuted)
A7_drop_directus drop --sandbox-id directus 4 off-limits/regex reject (A7 refuted)
A7_drop_postgres drop --sandbox-id postgres 4 reject
A7_drop_template1 drop --sandbox-id template1 4 reject
A8_create_force create … --force 4 --force DISABLED (A8 refuted)
create_no_args create 3 ADMISSION_DENIED
create_bad_ttl --ttl 99x 4 REFUSE_BAD_TTL
create_unknown_arg --bogus 2 unknown arg
drop_no_args drop 3 --sandbox-id required
vocab_bad_name --sandbox-id 'bad name with spaces' 4 regex reject
verify_unknown_arg --nope 2 unknown arg
vocab_absent_validname --sandbox-id c1_staging_20990101_0000 5 refuses absent sandbox (read-only)
drop_absent_validname --sandbox-id c1_staging_20990101_0000 0 idempotent NO_OP

POST-TEST staging_DBs = 0 — the hostile harness created no database.

Conclusion

A1, A2, A7, A8, A15 are refuted by live execution evidence (exit codes) plus code reading. No shell metacharacter, quote, semicolon, or whitespace can survive STG_SANDBOX_RE, and that assertion runs before any DB op or SQL interpolation in all 7 scripts.

Back to Knowledge Hub knowledge/dev/laws-new/reports/c1-staging-claude-r3-hard-self-gate/03-shell-injection-review.md