The repository fails CI when a workflow uses a mutable action reference, a pinned
action lacks a version comment, maintained executable setup code introduces an
ungoverned remote download or execution path, a job requests write permission
outside the allowlist, a committed secret is detected (the secret-scan gitleaks
job, plus the same hook locally via .pre-commit-config.yaml), or a dependency
carries an unexcepted high/critical advisory.
Capture the audit to a file before gating it. pnpm audit exits non-zero
whenever it finds any advisory — including the moderate and low ones the gate
deliberately ignores — so under set -o pipefail (which is how GitHub Actions
runs every run: block) piping the two together fails on the audit's exit code
regardless of the gate's verdict.
Container actions must use an image digest. Local actions under
./ are allowed. The same rule covers .pre-commit-config.yaml: every remote
hook repo pins rev: to a 40-character commit SHA with a version comment
(# v8.21.2); local and meta repos carry no rev. scripts/supply-chain-policy.mjs
enforces both, so reverting a hook to a tag fails the gate. Renovate's GitHub Actions manager has pinDigests: true, so
updates remain reviewable proposals and preserve immutable references.
Every workflow declares read-only permissions at the workflow level. Jobs that
publish a GitHub Release or deploy GitHub Pages opt into only the write or OIDC
permissions they need:
The secret-scan CI job runs the gitleaks CLI image (v8.21.2, pinned by digest)
over the full history (fetch-depth: 0) through scripts/gitleaks-scan.sh,
in two steps:
History scan. The container runs as root over a runner-owned checkout, so
git refuses the repository ("detected dubious ownership") unless it is a
safe.directory. When that happens gitleaks logs failed to scan Git
repository and still exits 0 with "no leaks found". The wrapper trusts the
checkout for its own process only (GIT_CONFIG_COUNT/GIT_CONFIG_KEY_0=safe.directory),
refuses a shallow clone, and fails unless the log shows N commits scanned
with N ≥ 1 and no read failure. Exit code alone is never the verdict.
Positive control. The same image and flags scan a scratch repository with
a freshly generated fake AWS access key planted in one commit; the step fails
unless gitleaks exits non-zero and reports aws-access-token. The key is
built at runtime, so no key-shaped literal is committed here.
tests/shell/gitleaks-scan.bats checks the log verdict against recorded gitleaks
output (including the dubious-ownership log) without Docker. To run the real
thing locally:
(From a git worktree, whose .git is a pointer file, scan a clone instead.)
The pre-commit gitleaks hook covers staged changes on the contributor's
machine.
Maintained shell, Python, and Node setup/automation under setup/, scripts/,
and .github/ may not download a remote input—or pipe one into a shell—without a
named, documented, unexpired entry in supply-chain/exceptions.json. The shell
scanner treats direct curl/wget, command substitution (x=$(curl …)),
eval "$(curl …)", and source <(curl …) as remote-input callsites.
Each exception binds an exact HTTPS source to one of two auditable kinds:
accepted-risk requires an exact, whitespace-normalized literal command
with no dynamic source. It states plainly that the bytes are not
checksum-pinned and gives the reason and expiry for that temporary risk
acceptance;
sha256 permits only one deliberately narrow flow: `curl -fsSL -o