Skip to content

fix: bind the heredoc sink allowlist to its own clause, fix the closing-delimiter scan, and drop the stale reserved-name exception - #485

Merged
fdaviddpt merged 2 commits into
mainfrom
fix/447-455-heredoc-sink-reserved-name
Oct 6, 2026
Merged

fdaviddpt merged 2 commits into
mainfrom
fix/447-455-heredoc-sink-reserved-name

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Closes #447
Closes #455

#447 -- heredoc sink allowlist fail-opened on 10 reproducible shapes

jit_heredoc_opener_is_known_sink() checked the WHOLE physical line for a known data sink (cat>/tee/supertool/git commit -F -/gh --body-file -), so a decoy sink sharing the line with a heredoc operator, separated by ;/&, made the allowlist match even though the heredoc belonged to a different, non-sink command (e.g. cat > /dev/null </dev/null; bash <<EOF). This also let round-1's fake-arithmetic-opener bug ((( y = 1<<EOF ))) hide behind the same decoy. Separately, the closing-delimiter scan kept only one optional quote/backslash character on each side of the bare identifier, so delimiters bash itself builds by concatenation (<<EOF"X", <<EOF-1, <<E\OF) were misread.

Fix: jit_heredoc_clause_at() binds both the sink check and the danger-token check to just the clause containing the << operator (quote-aware, bounded by the nearest unquoted ;/&), and jit_heredoc_scan_word() replays bash's own word-formation rule character by character to compute the real closing delimiter.

TDD: 8 new assertions in tests/test-pre-tool-hook.sh written before the fix, verified red (7/8 failing) by stashing only scripts/common-awk.sh, green after restoring. Verified identical behavior across /usr/bin/awk, /usr/local/bin/gawk and /opt/homebrew/bin/mawk.

#455 -- stale reserved-name exception in the release smoke test

_only_reserved_name_error() in .github/scripts/smoke_release_tree.py downgraded exactly one claude plugin validate --strict error (a reserved-name error) to a warning -- moot since #452 renamed the plugin away from the claude- prefix that triggered it -- and had a separate parsing bug where a second real error under an unrecognized per-file heading was never counted, letting a genuine second error through. Removed the function and its call site entirely so any validate failure now fails the smoke test outright.

TDD: rewrote tests/test-smoke-reserved-name-445.sh to assert the exception is gone, verified red (3/7) against the unfixed source, green (7/7) after.

Self-review

Two reviewers (an Explore-type reviewer and oss:auditor) ran against the committed diff. Findings and dispositions:

Tests

tests/test-pre-tool-hook.sh: 422/422 (was 398/398 before this change). tests/test-smoke-reserved-name-445.sh: 7/7. python3 .oss/assemble_changelog.py --check: passed. Full suite not run as the gating check (CI is the three-platform authority per this repo's own convention); it was run once early to rule out two pre-existing, unrelated failures as regressions.

🤖 Generated with Claude Code

[AI-generated]

Florian DAVID and others added 2 commits October 6, 2026 15:38
…ng-delimiter scan, and drop the stale reserved-name exception (#447, #455)

#447: jit_heredoc_opener_is_known_sink() checked the WHOLE line for a known
data sink, so a decoy sink separated by ; or && (cat > /dev/null </dev/null;
bash <<EOF) made the allowlist match a different, non-sink command's own
heredoc -- including the round-1 fake-arithmetic-opener shape hiding behind
the same decoy. The closing-delimiter scan also kept only one optional quote
or backslash on each side of the identifier, so EOF"X", EOF-1 and E\OF all
closed at the wrong line. Binds the sink check (and the danger-token check)
to the operator's own clause, and replays bash's own word-formation rule to
compute the real delimiter.

#455: _only_reserved_name_error() in smoke_release_tree.py downgraded
`claude plugin validate --strict`'s reserved-name error to a warning -- moot
since #452 renamed the plugin away from the claude- prefix that triggered it
-- and could separately mask a second real error under an unrecognized
heading. Removed entirely; any validate failure now fails the smoke test.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
…, skip-vs-fail exit code, engine-matrix coverage

Explore review and oss:auditor both ran against commit 2fe319c. Three real
findings, all fixed here; one real but pre-existing (not a #447 regression)
finding logged to trap.d/ rather than fixed inline or filed, since it ranks
non-blocking (misreports) in the manager skill's own table.

- jit_heredoc_clause_at() scanned for a bare ";"/"&" with no quote tracking,
  so a quoted one inside the operator's own clause could forge a fake
  boundary. Threaded the per-line quote state through it (the same
  quote_in[i] jit_heredoc_opener_is_suppressed already uses) so a ";"/"&"
  only ends a clause when the state at that character is 0. Also updated
  two stale "whole line"/"same line" comments left behind by the earlier
  clause-binding change.
- tests/test-smoke-reserved-name-445.sh's new wrapper folded a legitimate
  SKIPPED (exit 2, no /bin/sh to run the fake claude shim on) into the
  script's own exit 1 -- tests/run-all.sh reads exit 2 as SKIPPED and
  anything else as FAILED, so this would have mislabeled a platform-driven
  skip as a real failure on a leg with no /bin/sh (Windows). Fixed to
  propagate exit 2 separately from the static check's own exit 1.
- The new #447 assertions (jit_heredoc_scan_word/jit_heredoc_clause_at)
  only ran under the default $PATH awk, not the awk/gawk/mawk matrix this
  file already runs #442's own heredoc state-machine assertions through.
  Extended the matrix to cover them too.

Logged to trap.d/447.sink-regexes-quote-blind.md: jit_heredoc_opener_is_known_sink()'s
five sink regexes are quote-blind, so a quoted ";"+sink-keyword inside a
non-sink command's own argument can still false-match as a sink. Confirmed
this reproduces identically on bda0282 (before any #447 change), so it is
not a regression; fixing it is a separate, larger change to the matching
primitives themselves.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@fdaviddpt
fdaviddpt merged commit 9f89f6e into main Oct 6, 2026
13 checks passed
@fdaviddpt
fdaviddpt deleted the fix/447-455-heredoc-sink-reserved-name branch October 6, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant