Skip to content

Exec-bit guard silently passes invocations it cannot resolve to a tracked path #3688

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

The exec-bit guard (scripts/guard-tracked-exec-bit.sh, added in #3535) decides which scripts must be tracked 100755 by matching paths that textually contain scripts/ or .github/:

readonly SCRIPT_PATH_RE='(\.github|scripts)/[A-Za-z0-9_./-]+\.sh'

A workflow step can change the directory a command resolves against:

- name: something
  working-directory: scripts
  run: ./foo.sh

The runner directly execs the tracked scripts/foo.sh and therefore needs its execute bit, but ./foo.sh matches no modelled path, so no occurrence is produced. Because another invocation elsewhere satisfies the guard's anti-vacuity check, the run reports success rather than reporting that it could not tell — the same silent-pass failure mode the guard exists to remove, one level up.

Raised by Codex review on #3667 and confirmed valid there.

Affected audience and impact

CI correctness only; no production authorization surface is involved. Impact today is latent, not live — measured on the current tree:

  • working-directory: appears 3 times, all in publish-kubescape-storage-hotfix.yaml, all working-directory: upstream (a checked-out third-party repo) whose run: blocks invoke git, go and docker — no repository script.
  • Relative ./*.sh invocations outside scripts/ and .github/: 0.

So nothing is being missed right now. The gap bites on the first step that combines a working directory with a relative script invocation, and it fails silent-green, which is the expensive direction.

Expected behaviour

A directly-invoked script that the guard cannot map to a tracked path must not pass silently. Either resolve the invocation against its step's working-directory (and equivalent cd in a run block), or fail closed naming the invocation it could not resolve, with the paved-road fix in the message.

Fail-closed is the smaller first increment and removes the silent-pass property on its own; resolution can follow. Note the ordering constraint found while fixing the sibling findings in #3667: any broadened extraction must still run through the prefix classifier, or bash ./foo.sh becomes a false positive — and false positives here fail every PR and merge-group run.

Acceptance criteria

  • A fixture with working-directory: plus a relative invocation of a script tracked 100644 does not exit 0.
  • A fixture where that same script is handed to an interpreter still passes, so the fix is not "flag every relative path".
  • The committed tree still reports the same directly-invoked script count (22 at time of writing), proving the change is conservative.
  • Against the pre-change guard the new assertions fail and every existing assertion passes.

Rough size

Small-to-medium. Fail-closed detection is a contained change to extraction plus the classifier call path; full working-directory resolution is the larger half and can be a separate increment.

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