Repository navigation
fix(detection): shebang tokenisation, ambiguous triggers, trampolines, prefix anchors (#3116, #3118, #3133, #3134) - #3127
Merged
Merged
Conversation
…#3116) shell's `sh` trigger fired on `tclsh`/`wish`/`jimsh`/`swift-sh`/`racketsh` and javascript's `node` on `ts-node`, because a trigger was tested as a bare substring of the whole shebang line. The bogus shebang language then contradicted the file's own extension, so the Identity Conflict Trap forced real Tcl/Swift/Scheme/TypeScript scripts to Tier 5 -- undeterminable, intensity 0.0, plus an "Identity Masking" anomaly flag that reads as a malware finding on an ordinary script. Now resolves the interpreter basename (handling `env`, `env -S`, and VAR=value forms) and matches a trigger exactly or with a pure version suffix, so `python` still names python3/python3.12 while never naming micropython. Triggers are tried longest-first, which also removes the registry-order dependency for shebangs (#3118). Verified: the six failing shebangs now resolve to their real language at Tier 0; bash/sh -e/pwsh/node/perl/regina/python3/python3.12/env -S/micropython controls unchanged. 9,513 core_engine+extraction tests pass (the one failure is the pre-existing fidelity-table pin staleness). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ution order (#3116, #3118) Follow-ups found by the #3118 invariant test, which asserts that every shebang trigger resolves to the language declaring it: 1. Three triggers are claimed by TWO languages -- `deno` and `bun` (both run TypeScript and JavaScript) and `csi` (Chicken Scheme's interpreter AND the C# script runner). Registry order picked a winner, so the other claimant's files contradicted their own extension and hit Tier 5. Measured on main: `.ts` + '#!/usr/bin/env deno run' and `.scm` + '#!/usr/bin/csi -s' both returned undeterminable with an "Identity Masking" flag -- the #3116 bug via a second route, and deno+TypeScript is a mainstream combination. Such triggers now yield NO shebang verdict and the extension decides, which is correct for every claimant (the refuse-rather-than-guess posture Tier 4 already takes). Both claimants of all three now classify correctly. 2. tcl's path-shaped `bin/expect` trigger only matched under the old substring behaviour; basename-normalising triggers at boot restores it and upgrades it to Tier 0 consensus. The trigger table is now built once in _calibrate_lookup_maps (normalised, longest-first, ambiguities dropped) instead of rebuilt per file. Also: source_proof now names the mechanism that actually fired. This method has always resolved shebangs AND internal discriminators, but every caller labelled the result "Shebang", so a .asm resolved by 'ACCTPGM CSECT' reported "Single Indicator (Shebang)" with no #! in the file, and an identity conflict raised against a discriminator reported a shebang that did not exist. source_proof is a load-bearing claim in this engine. Adds tests/core_engine/test_detection_resolution_order.py (81 cases): the claimant table per contested extension in registry order, the invariant that every multi-claimant extension is a registered collision, per-claimant behavioural resolution for 18 pairs, the shebang order-independence invariant, the ambiguity pin, and the #3116 regression matrix. 9,594 core_engine+extraction tests pass (the one failure is the pre-existing fidelity-table pin staleness). ruff/mypy audits baseline-clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
…rate golden masters (#3116) The crucible audit surfaced two things token matching exposed: 1. Nine files dropped from Tier 0 consensus to Tier 2 extension-only because their real interpreters were never in any shebang list -- `#!/bin/csh` and `#!/bin/tcsh`, `#!/usr/bin/pfsh` (Solaris profile shell), `#!/bin/Shell` (SerenityOS), and `#!/usr/local/bin/luatrace` (darwin-xnu's tracing scripts). All five resolved under the old behaviour only because `sh`/`lua` matched as a bare SUBSTRING of the line, so the same bug was causing false positives (tclsh -> shell) AND covering for these false negatives. Named explicitly now; all nine are back at Tier 0, intentionally. 2. Golden-master drift, both environments, fully explained: 243 diffs, ALL of them `Single Indicator (Shebang)` -> `Single Indicator (Internal Signature)` (239) or the same correction on the consensus path (4). Same language, same lock tier, on every one -- these are files resolved by their internal discriminator that the proof string used to attribute to a shebang they do not have. Zero language flips, zero tier changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Sep 17, 2026
squid-protocol
added a commit
that referenced
this pull request
Sep 17, 2026
…rank a prefix (#3133, #3134) (#3136) * fix(detection): clear trampoline bootstraps; let a real extension outrank a prefix (#3133, #3134) Re-landed on current main. These two fixes were written on the #3127 branch but that PR merged as a SQUASH of only its first half, so they never reached main -- `grep -c _trampoline_interpreter` on main returns 0. The original branch is also based on the pre-#3128/#3130 main, so merging it now would have DELETED the detection-accuracy harness and reverted the archetype parity gate; this branch is cut fresh from main with only the two source files. #3133 -- `#!/bin/sh` + `exec tclsh "$0" ${1+"$@"}` is the canonical Tcl portability idiom (the crucible's two cases are sqlite's own test-harness header verbatim). The shebang genuinely IS `sh`, so it legitimately contradicts the `.tcl` extension, and the Identity Conflict Trap read that as masquerading: Tier 5, `undeterminable`, plus an "Identity Masking" flag on a stock sqlite script. A generic shell shebang is a bootstrap vehicle; the `exec <interpreter>` in the opening lines is what the file runs as. Narrow by construction: only an interpreter the EXTENSION's own language claims clears the conflict, and only for a shell-family shebang, so a `.tcl` that re-execs python3 still trips the trap. #3134 -- `BUILD.mk` locked to python at Tier 1 via `Prefix Anchor (BUILD)` before its own `.mk` was consulted. An extension now outranks a filename PREFIX; an EXACT filename match (`Makefile`, `Dockerfile`) still wins outright. Scoped so a TEMPLATE extension does not outrank a prefix either -- `.in` names a template, not a language, so `Makefile.pre.in` (which IS Makefile syntax) stays makefile instead of following `.in` to m4. The unscoped version flipped that file and the crucible caught it. Measured on the #3117 harness, this base: overall 0.9964 -> 0.9974, auto subset 0.9984 -> 0.9996, refusals 3 -> 1, Tier 5 conflicts 3 -> 1, errors 11 -> 8. Detection baseline regenerated so the gate holds the improvement rather than the old looser number. Golden masters regenerated in both environments from the CORRECT base. 87 resolution-order tests (up from 81). Full suite 10,048 pass; the 12 failures are pre-existing (11 numpy/xgboost-absent security_auditor, 1 fidelity pin). ruff/mypy baseline-clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: describe the trampoline shebang instead of quoting it (X-Ray) The comment documenting #3133's idiom quoted a literal shell shebang, and a '#!' + '/bin/' sequence anywhere in a file's first 8 KiB trips the X-Ray binary-anomaly detector's embedded-execution-header check. Its exemption is scoped to .sh/.bash/.zsh/.command extensions, so a .py file DOCUMENTING a shebang is flagged. Described rather than quoted, with a note in place so a future reader does not helpfully restore the literal and re-break CI. X-Ray now reports 0 anomalies; 87 resolution-order tests still pass. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3116, closes #3118.
The bug
A shebang trigger was tested as a bare substring of the whole line (
if trigger in first_line), so shell'sshfired ontclsh/wish/jimsh/swift-sh/racketshand javascript'snodefired onts-node. The bogus shebang language then contradicted the file's own extension, and the Identity Conflict Trap forced the file to Tier 5 — Absolute Distrust:undeterminable, intensity 0.0, plus anIdentity Maskinganomaly flag. A customer with an ordinary Tcl script didn't see a classification error; they saw the scanner accusing their file of masquerading.Measured on main before the fix:
hello.tcl#!/usr/bin/tclshundeterminabletclTier 0gui.tcl#!/usr/bin/wishundeterminabletclTier 0j.tcl#!/usr/bin/env jimshundeterminabletclTier 0s.swift#!/usr/bin/env swift-shundeterminableswiftTier 0r.scm#!/usr/bin/env racketshundeterminableschemeTier 0t.ts#!/usr/bin/env ts-nodeundeterminabletypescriptTier 0#!/usr/bin/pwshonly ever passed by accident of registry order (powershell precedes shell), not by design.The fix
Resolve the interpreter basename and match it as a token: handles the plain path form,
env,env -S, andVAR=valueprefixes. A trigger matches exactly, or with a pure version suffix — sopythonstill namespython3andpython3.12while never namingmicropython.Two more bugs the #3118 invariant test then found
Writing "every trigger must resolve to the language that declares it" surfaced two further defects, both fixed here:
1. Three triggers are claimed by two languages each.
denoandbunboth run TypeScript and JavaScript;csiis both Chicken Scheme's interpreter and the C# script runner. Registry order picked a winner, so the other claimant's files contradicted their own extension and hit Tier 5 — the same bug via a second route. Measured on main:.ts+#!/usr/bin/env deno runand.scm+#!/usr/bin/csi -sboth returnedundeterminablewith an Identity Masking flag, and deno+TypeScript is a mainstream combination.Such triggers now yield no shebang verdict and the extension decides — correct for every claimant, and the same refuse-rather-than-guess posture Tier 4's margin requirement already takes. All four claimants (
.ts/.js/.scm/.csx) now classify correctly.2. tcl's path-shaped
bin/expecttrigger only matched under the old substring behaviour. Basename-normalising triggers at boot restores it, and upgrades it:#!/usr/bin/expecton a.tclfile now reaches Tier 0 consensus instead of extension-only Tier 2.The trigger table is now built once in
_calibrate_lookup_maps(normalised, longest-first, ambiguities dropped) rather than rebuilt per file.Also:
source_proofnow names the mechanism that fired_tier_2_fingerprint_checkhas always resolved shebangs and internal discriminators, but every caller labelled the resultShebang. So a.asmresolved byACCTPGM CSECTreportedSingle Indicator (Shebang)with no#!anywhere in the file, and an identity conflict raised against a discriminator match reported a contradiction with a shebang that didn't exist.source_proofis a load-bearing claim in this engine — the mechanism now travels with the verdict (ShebangvsInternal Signature).Tests
tests/core_engine/test_detection_resolution_order.py, 81 cases:bin/expectcase, and the six Shebang matching is unanchored substring search: canonical tclsh/wish/ts-node shebangs trigger false Identity Conflicts (Tier 5) #3116 regressions9,594
core_engine+extractiontests pass; the single failure is the pre-existing fidelity-table pin staleness. ruff/mypy audits baseline-clean.🤖 Generated with Claude Code
Update: two more fixes, found by the #3117 harness
Once the harness (#3130) existed, it was run against this branch and surfaced four more defects. Two are fixed here; the third is disproven twice and documented; the fourth is left open.
#3133 — the portable-trampoline idiom
#!/bin/shfollowed byexec tclsh "$0" ${1+"$@"}is the canonical Tcl portability trick, and the crucible's two cases are sqlite's own test-harness header verbatim. The shebang genuinely issh, so it legitimately contradicts the.tclextension — and the Identity Conflict Trap read that as masquerading and refused the file at Tier 5 with anIdentity Maskingflag. A customer would see the scanner call a stock sqlite script a disguised payload.A generic shell shebang is the standard bootstrap vehicle; an
exec <interpreter>in the opening lines is what the file actually runs as. The escape hatch is deliberately narrow: only an interpreter the extension's own language claims clears the conflict, and only when the conflicting shebang resolved to the shell family. A.tclthat re-execspython3still trips the trap — pinned by a test.Note this is not the #3116 bug and was not fixed by the original commits here: there the matcher was wrong, here it is right.
#3134 — a real extension outranks a filename prefix
BUILD.mklocked to python at Tier 1 viaPrefix Anchor (BUILD)before its own.mkwas consulted. Now an extension outranks a prefix, while an exact filename match (Makefile,Dockerfile) still wins outright — those have no extension to defer to.Scoped so a template extension does not outrank a prefix either:
.innames a template, not a language, soMakefile.pre.in(which is Makefile syntax) staysmakefilerather than following.into m4. The unscoped version flipped that file, and the crucible caught it before it shipped.#3132 — not fixed, and two candidate fixes measured and rejected
Both plausible fixes make detection materially worse, and both are now recorded as comments at the relevant lines so nobody re-derives them:
Both break 32 sqlite files to db2_sql to recover 7 MicroPython files. The root cause is sharper than the issue's original framing:
pythonlists.pyas its owndiscriminatorsentry, which is literally where the reported "72% Local Dominance" comes from (base 14 + 14×2 = 42 against embedded_python's 14 + 1×2 = 16) — and that same accidental self-reference is what currently holds.sqltogether. It is load-bearing by accident, and.sqlneeds a real content signal before it can be removed.I would have shipped the first of those two without the harness. That is the clearest argument for #3130 I can offer.
Measured effect of what did land
The 8 remaining are 7 × #3132 and 1 × the sh/PowerShell polyglot (
download.ps1, which has noexecline and so is genuinely a different shape from the trampoline).Golden masters
Regenerated in both environments, drift fully explained: 462 diffs but only three identity-level changes —
LICENSE.adoc's proof and tier (same language), and the excluded-artifacts queue shrinking 548 → 546 as the two trampoline files join the analysed population.tclcomposition goes 29 → 31, and the remaining ~459 diffs are that group's aggregates and population-relative z-scores recomputing around two new members. Zero language flips.87 resolution-order tests (up from 81); 9,600
core_engine+extractionpass.