Skip to content

fix(core-engine): shell reflection counts dispatch, not command substitution (#2722) - #2725

Merged
squid-protocol merged 1 commit into
mainfrom
fix/2722-shell-reflection
Sep 4, 2026
Merged

fix(core-engine): shell reflection counts dispatch, not command substitution (#2722)#2725
squid-protocol merged 1 commit into
mainfrom
fix/2722-shell-reflection

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

What

shell's reflection_metaprogramming rule counted two pieces of ordinary shell as metaprogramming.
Since #2719 that count is the file's dynamism -- read by documentation risk (weight 1.0) and
cognitive-load heat (x5), on top of the security metrics that already read it -- so the over-match
became structural rather than cosmetic. Closes #2722.

Measured over the crucible's 255 shell files (26,274 coding LOC), each alternative counted
independently (the sum exceeds the rule's 4,863 hits because $(...) swallows nested matches):

alternative hits share reading verdict
${var} 4,117 84.7% a variable expansion -- matches only because !? made the indirection marker optional bug
$(...) 789 16.2% runs a program and substitutes its output, one per 33 lines dropped
`...` 164 3.4% the same construct in older spelling dropped
awk|sed|perl|python|ruby '...' 152 3.1% a program of another language written in a string kept
eval $ 5 0.1% real dynamic execution, but the $ anchor missed eval "$cmd" widened
${!var} 0 0.0% indirect expansion -- the construct the alternative exists for kept

${!var} never occurs in the corpus at all: every one of those 4,117 hits was a plain variable.

The registry settles the $(...) question. Every other language's rule counts name-based dynamic
dispatch and introspection
-- python getattr/setattr/inspect., go reflect./unsafe./cgo,
ruby method_missing/define_method, js Reflect/Proxy/__proto__. None counts invoking another
program; python's rule does not fire on subprocess.run. $(date) is that same call, and its result
is data, not code. It is the same shape as the control_flow_ratio vocabulary leak in #2705.

The rule

Before:

\$\((?:[^()]|\([^()]*\))+\)|`[^`]+`|\b(?:awk|sed|perl|python[23]?|ruby)\s+['"][^'"]{0,500}|\beval\s+\$|\$\{!?[a-zA-Z0-9_]+\}

After:

\beval\b
|\$\{![a-zA-Z0-9_]+\}
|\b(?:declare|typeset|local)[ \t]+-n\b
|(?:^|[ \t;|&])(?:source\b|\.(?=[ \t]))[ \t]+["']?\$
|\b(?:awk|sed|perl|python[23]?|ruby)\s+['"][^'"]{0,500}

Dropped: $(...), backticks, and the !? that admitted ${var}. Kept: the inline sub-language
programs and ${!var} -- which is exactly what the rule's own comment always claimed it was,
"sub-languages and indirect expansion". Added, per the issue's suggested fix: eval unanchored,
namerefs declare/typeset/local -n, and source/. of a computed path.

The overlaps with pointers (${!, declare -n) and safety_bypasses (eval) are deliberate and
pre-existing -- opacity signals overlap across the registry and each consumer reads its own.
Considered and left out: trap '...' SIG (code in a string, but owned by cleanup) and a
"runs a command" home for $(...), which would be a new signal, not this fix.

Effect: 4,863 -> 227 hits, 73 of 255 files still carrying one. configure-helper.sh, the issue's
worst case, goes 1,019 -> 15.

Tests

  • test_shell_strict.py's signature pair is now ('reflection_metaprogramming', 'eval "$cmd"', 'output=$(date)') -- yesterday's positive is today's negative, which is the finding itself.
  • The nested-$(...) regression became test_shell_reflection_metaprogramming_is_dispatch_not_vocabulary:
    ${var}, $(date), `ls` and the DIR=$(cd "$(dirname "$0")" && pwd) idiom must NOT match;
    eval, ${!name}, namerefs, computed source and inline awk must.
  • The ReDoS test is repointed at the surviving alternatives (${!, source $, awk ' runs) and
    keeps the historical $( input, now asserted immune and unmatched.

Gauntlet

  • Full suite: 7566 passed, 3 skipped, 9 xfailed, 3 xpassed.
  • audit_check.py --regenerate: all clear (ruff 70-finding baseline + format clean, mypy 2-error
    baseline, dead-key, ast-accuracy) -- no baseline regenerated.
  • tree_sitter_accuracy_audit --ci --all: 30/30 OK. tri_comparison_chart --all --ci: 3/3 OK.
    rosetta_audit: 46 languages, 0 regressions.

Golden master

Both fixtures re-blessed. 508 diffs, 0 topological, and every substantive one is a shell file:

language diffs note
shell 454 the corpus's own shell/ tree
fortran 21 wrf's .sh/.csh/.ksh build and regression scripts
zig 4 tigerbeetle/download.sh
(ecosystem summary) 27 the roll-ups of the above

Every other language in the fixture has zero diffs. Movement is down, as expected -- Metaprogramming
& Reflection mean -37.87 (max -1004 on configure-helper.sh), Cognitive Load mean -19.07,
Documentation mean -34.70. 20 files' Documentation Exposure falls by >=25, which is the reversal of the

=25 jumps #2719's attribution flagged and referred here.

Exactly one value in the whole corpus goes up: shell/sqlite/tclConfigShToMake.sh gains 1
reflection hit for . "$1" -- dot-sourcing a path taken from $1, in a file whose own header comment
calls itself "a level of indirection". That is the new source/. alternative doing its job.

reflection_metaprogramming is not in any expected_signals.json, so keyword-rosetta is blind to the
change and no corpus re-bless is owed; the two hits the new rule finds there are both eval : in
shell/b.sh, planted as safety_bypasses.

Part of #2716.

…itution (#2722)

`shell`'s `reflection_metaprogramming` rule counted two pieces of ordinary
shell as metaprogramming. Since #2719 that count IS the file's dynamism --
read by documentation risk and cognitive-load heat on top of the security
metrics that already read it -- so the over-match became structural.

  * `\$\{!?[a-zA-Z0-9_]+\}` made the indirection marker OPTIONAL, so plain
    `${var}` matched: 4,117 of the crucible's 4,863 shell hits (85%) were
    variable expansions, while `${!var}` itself never occurs in the corpus.
  * `$(...)` and backticks counted every command substitution, one hit per
    ~33 lines of real shell. Running a program yields data, not code, and no
    other language's rule counts invocation -- python's does not fire on
    `subprocess.run`. Same shape as the `control_flow_ratio` leak in #2705.

Both dropped. What remains is dispatch decided at runtime by a name, which
is what the rule's own comment always claimed it was: inline sub-language
programs and `${!var}`, plus (per the issue) `eval` unanchored -- the old
`\beval\s+\$` missed `eval "$cmd"` -- namerefs, and `source`/`.` of a
computed path. 4,863 -> 227 hits, 73 of 255 files still carrying one.

Golden master re-blessed, both venvs: 508 diffs, 0 topological, all of them
shell files (the `shell/` tree plus wrf's .csh/.ksh scripts and
tigerbeetle/download.sh), every other language zero. Exactly one value rises:
`tclConfigShToMake.sh` gains a hit for `. "$1"`, the new computed-source
alternative doing its job. `reflection_metaprogramming` is unplanted in
keyword-rosetta, so no corpus re-bless is owed.

Part of #2716.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

@squid-protocol
squid-protocol merged commit 07e7d1a into main Sep 4, 2026
31 checks passed
@squid-protocol
squid-protocol deleted the fix/2722-shell-reflection branch September 4, 2026 17:08
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