🤖 Generated by the Agentic Engineer
Evidence
scripts/guard-tracked-exec-bit.sh fails open on two invocation spellings, so a script the
repository genuinely execs can be tracked 100644 and the guard still reports success. Measured
at 482d6b88 (the head that fixed the ; case), with the target tracked 100644 and an
executable anchor present so anti-vacuity is satisfied:
invocation in a run: step |
guard |
expected |
echo ready| scripts/x.sh |
rc=0 |
1 |
echo ready&& scripts/x.sh |
rc=0 |
1 |
echo ready; scripts/x.sh (fixed at 482d6b88) |
rc=1 |
1 |
echo ready; bash scripts/x.sh (control) |
rc=0 |
0 |
There is a third, purely extractive leak in the same family: with no space at all,
echo ready;scripts/x.sh produces no occurrence to classify, because the run: extraction form
requires whitespace after each leading token.
Affected audience and impact
This is the guard that exists because a promotion gate shipped inert — committed 100644 while a
composite action invoked it directly, so the step died permission denied before verifying
anything, and shellcheck, bash <file> and the script's own suite were all green. Every spelling
that escapes the guard is a way for that same class to ship again unnoticed.
Why a classifier fix cannot reach these two
482d6b88 splits a glued ; inside the prefix walk, which is correct for the case it targets.
These two fail one level earlier:
ready&& scripts/x.sh never reaches the classifier as two commands, because segmentation splits
on [[:space:]]+&&[[:space:]]+ and there is no leading space.
ready| scripts/x.sh is a single word, so the | reset arm in the walk never matches it.
Expected behaviour
A separator ends a command wherever it appears, so each of the spellings above is judged as two
commands and the directly-invoked 100644 path is rejected.
Acceptance criteria
Size
Small. One normalization pass plus fixtures.
Note
A working implementation already exists and is verified against all of the above — a quote-aware
pass that pads ;, |, &&, || and ( into standalone tokens ahead of extraction,
segmentation and classification, so no stage needs teaching another spelling. It was not pushed
because #3667 was actively owned at the time. See
#3667 (comment).
Evidence
scripts/guard-tracked-exec-bit.shfails open on two invocation spellings, so a script therepository genuinely execs can be tracked
100644and the guard still reports success. Measuredat
482d6b88(the head that fixed the;case), with the target tracked100644and anexecutable anchor present so anti-vacuity is satisfied:
run:stepecho ready| scripts/x.shecho ready&& scripts/x.shecho ready; scripts/x.sh(fixed at482d6b88)echo ready; bash scripts/x.sh(control)There is a third, purely extractive leak in the same family: with no space at all,
echo ready;scripts/x.shproduces no occurrence to classify, because therun:extraction formrequires whitespace after each leading token.
Affected audience and impact
This is the guard that exists because a promotion gate shipped inert — committed
100644while acomposite action invoked it directly, so the step died
permission deniedbefore verifyinganything, and shellcheck,
bash <file>and the script's own suite were all green. Every spellingthat escapes the guard is a way for that same class to ship again unnoticed.
Why a classifier fix cannot reach these two
482d6b88splits a glued;inside the prefix walk, which is correct for the case it targets.These two fail one level earlier:
ready&& scripts/x.shnever reaches the classifier as two commands, because segmentation splitson
[[:space:]]+&&[[:space:]]+and there is no leading space.ready| scripts/x.shis a single word, so the|reset arm in the walk never matches it.Expected behaviour
A separator ends a command wherever it appears, so each of the spellings above is judged as two
commands and the directly-invoked
100644path is rejected.Acceptance criteria
; bash scripts/x.sh) still returns 0, so the relaxation is notsatisfied by demanding the bit everywhere.
position (
echo "done;" scripts/x.shstays accepted).after, with the comparison ablation-tested so it is not vacuous.
Size
Small. One normalization pass plus fixtures.
Note
A working implementation already exists and is verified against all of the above — a quote-aware
pass that pads
;,|,&&,||and(into standalone tokens ahead of extraction,segmentation and classification, so no stage needs teaching another spelling. It was not pushed
because
#3667was actively owned at the time. See#3667 (comment).