Skip to content

fix: harden codex helper scripts (prompt substitution, state keys, preflight)#3

Open
cdgraham wants to merge 1 commit into
PiLastDigit:masterfrom
cdgraham:fix/codex-script-hardening
Open

fix: harden codex helper scripts (prompt substitution, state keys, preflight)#3
cdgraham wants to merge 1 commit into
PiLastDigit:masterfrom
cdgraham:fix/codex-script-hardening

Conversation

@cdgraham

Copy link
Copy Markdown

While reviewing the codex helper scripts for security/bug issues, I found a few real defects and hardened them. All changes are in the shared script layer (codex-plan-review/scripts/) plus one new file for codex-implement.

Fixes

1. load_prompt corrupted values containing & or backslashes (confirmed bug)

awk's gsub() treats & in the replacement as "the matched text", so implementer notes like Fixed X & Y rendered into the reviewer prompt as Fixed X {{IMPLEMENTER_NOTES}} Y, and awk -v escape processing mangled backslashes (C:\Users, \n in notes). Since --notes is free-form prose written every resume round, this hits real usage.

Rewrote substitution in bash with quoted replacement expansions: ${content//'{{TARGET}}'/"${TARGET-}"}. The quoting matters — unquoted bash patsub has the exact same & bug on bash ≥ 5.2 (patsub_replacement is on by default there). Verified against hostile inputs (&, backslashes, $, multiline) on bash 5.2.

2. target_key collisions

foo/bar and foo__bar (or release?1 / release_1) sanitized to the same key and silently shared one Codex thread + review file. The key now appends a cksum of the resolved target; same file via different relative paths still maps to one key.

⚠️ Migration note: the key format changed, so in-flight sessions from older versions won't be found — run reset.sh/start.sh fresh after upgrading. State is disposable session data, so nothing is lost beyond thread continuity.

3. Missing jq killed start.sh silently

jq's stderr was redirected to /dev/null, so with set -e a missing jq aborted the script with no message at all — after the Codex call already ran, orphaning the session. Added a require_tools preflight (codex jq in start, codex in resume) that fails loudly up front.

4. Empty thread file produced a confusing failure

An empty .thread file passed the -f check and invoked codex exec resume "". resume.sh now exits 2 with a "run reset.sh, then start.sh" hint.

5. Model selection glob could misfire

case "$STATE_DIR" in *codex-implement*) matches anywhere in the path — a repo checked out at e.g. ~/work/codex-implement-fork/ would run all review threads on the implementation model. Now matches the trailing */codex-implement/state components exactly.

6. codex-implement shipped no state/.gitignore

Both review skills ship one precisely so thread ids, reports, and raw JSONL event logs (which embed prompts and chunks of the user's codebase) never get committed in target repos, but the implement skill's state/ is created at runtime unignored. Added the same .gitignore.

README

Added a short caveat to the Multi-Agent section: implementer --notes are injected verbatim into the reviewer's prompt and can steer it — worth knowing when a review converges suspiciously fast.

Verification

bash -n on all scripts; functionally exercised load_prompt (hostile/multiline values), target_key (collision + path-stability cases), require_tools, model selection for both state paths, and the show.sh/reset.sh/resume.sh flows against a scratch state dir.

🤖 Generated with Claude Code

…eflight)

- load_prompt: replace awk gsub with quoted bash substitution — values
  containing '&' or backslashes (implementer notes, paths) were mangled;
  unquoted bash patsub has the same '&' bug on bash >= 5.2
- target_key: append cksum of resolved target so distinct targets that
  sanitize identically no longer share thread/review state (key format
  change: in-flight sessions from older versions need a fresh start.sh)
- require_tools preflight in start/resume: missing jq previously killed
  start.sh silently (set -e + suppressed stderr) after the Codex call
- resume.sh: fail cleanly on empty thread file instead of invoking
  'codex exec resume ""'
- model selection: match */codex-implement/state exactly, not glob
  *codex-implement* which a repo path could accidentally satisfy
- codex-implement: ship state/.gitignore like the review skills so
  reports/event logs can't be committed in target repos
- README: caveat that implementer --notes are injected into the
  reviewer prompt and can steer it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant