Skip to content

guard-tracked-exec-bit: a separator glued to the preceding token still fails open on '|' and '&&' #3693

Description

@devantler

🤖 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

  • A fixture per spelling above, each failing against the current guard and passing after.
  • The interpreter control (; bash scripts/x.sh) still returns 0, so the relaxation is not
    satisfied by demanding the bit everywhere.
  • A false-positive control proving a separator inside a quoted string does not create a command
    position (echo "done;" scripts/x.sh stays accepted).
  • Conservation: the guard resolves the same directly-invoked set on this repository before and
    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 #3667 was actively owned at the time. See
#3667 (comment).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions