KB-6F3C

Fix 1 — Injection / Shell-Safety

3 min read Revision 1
c1stagingcodex-r1-fixinjection2026-06-23

03 — FIX 1: INJECTION / SHELL SAFETY (_common.sh)

Root cause (Codex A13)

Rev-1 ran docker exec postgres sh -lc "psql … $* …". The $* joined the caller's psql args (-v purpose="…" -v owner="…" -v ttl="…") into a single string that the container sh -lc RE-PARSED. User-controlled purpose/owner/ttl could become shell syntax inside the container.

Fix

psql is now invoked as explicit argv directly (no shell on the container side):

docker exec "${PG_CONTAINER}" psql -U "$(stg_pg_user)" -v ON_ERROR_STOP=1 "$@" -d "$db" -f "$rmt" </dev/null
  • "$@" passes every caller arg as a separate argv word to psql — never re-split by a shell.
  • stg_pg_user resolves the role once via docker exec postgres printenv POSTGRES_USER (fixed args; no user input) → directus. Not hardcoded.
  • Temp files are created inside the container with mktemp /tmp/c1stg.XXXXXXXXXX (unpredictable) and tracked in STG_REMOTE_TMPS; a trap stg_cleanup_remote_tmps EXIT guarantees removal even on failure.
  • stg_scalar and stg_drop_db use the same argv-safe pattern.
  • New stg_assert_ttl constrains TTL to ^[0-9]+[hd]$ before use.

Why user data can no longer reach a shell or SQL

  • purpose/owner/ttl arrive at psql as literal -v name=value argv words → used in SQL as :'name' (psql-quoted literal). No shell, no SQL injection.
  • sandbox names are regex-validated ^c1_staging_[0-9]{8}_[0-9]{4}$ before any interpolation, so ${SBX} embedded in scalar SQL cannot contain quotes/metacharacters.
  • stg_drop_db asserts the sandbox name then embeds it via printf '… "%s" …' — digits-only, no injection.

Static proof (deployed files)

grep -RnE '\$[*@]'  bin/  -> ONLY: _common.sh:76  docker exec … psql … "$@" …   (quoted argv passthrough)
grep -RnE 'eval|sh -c|sh -lc|bash -c'  bin/  (non-comment) -> (none)
grep -RnE 'docker exec.*sh -lc|.*sh -c' bin/ (non-comment) -> (none)

Guard self-tests (pure-bash helpers, NO primitive invoked, NO DB op):

stg_assert_sandbox_name directus                              -> rc=4 (refused off-limits/regex)
stg_assert_sandbox_name postgres                              -> rc=4
stg_assert_sandbox_name c1_staging_2026                       -> rc=4 (bad format)
stg_assert_sandbox_name c1_staging_20260623_0711             -> rc=0
stg_assert_sandbox_name 'c1_staging_..._0711; DROP DATABASE directus' -> rc=4 (injection-style refused)
stg_assert_ttl 24h / 7d                                       -> rc=0
stg_assert_ttl 99x / 24                                       -> rc=4

bash -n OK; shellcheck CLEAN. sha256 bin/_common.sh = 1b2d13d0604e500136526f09eafec75ba90648508a6788146cd53e02bef57f8c.

Back to Knowledge Hub knowledge/dev/laws-new/reports/c1-staging-codex-r1-fixes-ready-for-r2/03-fix-injection-risk.md