Files
certctl/scripts/ci-guards/no-tag-pinned-actions.sh
shankar0123 95cb002905 ci: supply-chain hardening (Phase 1 closure — RED-1, RED-2, TEST-L2)
Three findings from the certctl architecture diligence audit's Phase 1
bundle (Supply-Chain Hardening) closed together in one PR since they all
touch .github/workflows/ + repo root.

RED-1 — delete tracked precompiled binary
  - deploy/test/f5-mock-icontrol/f5-mock-icontrol (8.6 MB ARM64 ELF) was
    tracked alongside the Go source that builds it. The fixture's
    Dockerfile already uses a multi-stage build that re-runs
    'go build' inside the container (line 13), so the tracked binary
    was vestigial — never actually consumed by the test wiring.
  - git rm'd. Path added to .gitignore so it doesn't re-land.
  - No Makefile target needed; the Dockerfile is the rebuild path.

RED-2 — SHA-pin every GitHub Action
  - Pre: 37 of 41 'uses:' lines were tag-pinned (@v4 etc); only
    4 were SHA-pinned (sigstore/cosign-installer + anchore/sbom-action).
  - Post: 0 / 41. Every 'uses:' line is now '@<40-char-sha>  # vN'
    (the trailing comment preserves the human-readable version for
    operator audit). SHA-pinning closes the standard supply-chain
    attack vector against GitHub Actions consumers.
  - SHAs resolved live via the GitHub API; spot-checked one.

TEST-L2 — npm audit hard gate
  - Added 'npm audit --omit=dev --audit-level=high' step to the
    Frontend Build job in ci.yml. --omit=dev excludes vitest/vite/
    eslint/etc which don't ship to operators.
  - Local run today: 0 vulnerabilities; gate enters with no triage
    backlog. Catches future regressions.

New CI guards (regression-prevention):
  - scripts/ci-guards/no-tag-pinned-actions.sh — fails the build if
    a future PR adds 'uses: foo/bar@v2' instead of SHA-pinning.
  - scripts/ci-guards/no-precompiled-binary.sh — runs file(1) over
    git ls-files output; fails on any tracked ELF/Mach-O/PE.
  - Both pass locally. CI's existing loop over scripts/ci-guards/*.sh
    picks them up automatically.

Closes: cowork/certctl-architecture-diligence-audit.html#fix-RED-1,
        cowork/certctl-architecture-diligence-audit.html#fix-RED-2,
        cowork/certctl-architecture-diligence-audit.html#fix-TEST-L2
2026-05-13 19:30:53 +00:00

53 lines
2.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# scripts/ci-guards/no-tag-pinned-actions.sh
#
# Phase 1 RED-2 closure (2026-05-13): every GitHub Action invocation
# under .github/workflows/ MUST be SHA-pinned (@<40-char-sha>) rather
# than tag-pinned (@v4 / @v0.10.0 / etc.). Tags are mutable; SHAs
# aren't. Mutable tags are the standard supply-chain attack vector
# against GitHub Actions consumers — a compromised tag silently
# pulls compromised code on every CI run.
#
# Pattern allowance:
# - `uses: org/repo@<40-char-sha> # v4` ← the trailing comment
# documenting the human-readable tag is REQUIRED for operator
# audit purposes ("which version is that SHA?"), but the SHA is
# the load-bearing pin.
#
# How to fix a violation:
# 1. Look up the action's tag → SHA mapping. Either via the GitHub
# web UI (visit the action's tags page), or via:
# curl -sS https://api.github.com/repos/<org>/<repo>/git/refs/tags/<tag> | jq .object.sha
# 2. Rewrite the line as `uses: <org>/<repo>@<sha> # <tag>`.
# 3. Re-run this guard locally to confirm.
#
# Rationale + history:
# - Phase 1 of the certctl architecture diligence remediation
# (cowork/certctl-architecture-diligence-audit.html#fix-RED-2)
# swept the entire .github/workflows/ tree from 37 tag-pinned /
# 4 SHA-pinned to 0 tag-pinned / 41 SHA-pinned in one PR.
# - This guard catches the regression mode: a future PR adds a new
# `uses: foo/bar@v2` line and the build fails until the
# contributor SHA-pins it.
set -e
# Match `uses: <anything>@vN` or `@v<digit>.<digit>` etc. Don't match
# `@<40-char-sha>`. The negative-lookahead-free shell-regex shape:
# anything-not-hex-40-chars after the @.
VIOLATIONS=$(grep -rnE "uses:[^#]*@v[0-9]" .github/workflows/ 2>/dev/null || true)
if [ -n "$VIOLATIONS" ]; then
echo "::error::no-tag-pinned-actions regression: tag-pinned uses: line found."
echo ""
echo "GitHub Actions MUST be SHA-pinned (@<40-char-sha>) rather than"
echo "tag-pinned (@v4). Tags are mutable; SHAs aren't. See the guard"
echo "header for the fix workflow."
echo ""
echo "Violations:"
echo "$VIOLATIONS"
exit 1
fi
echo "no-tag-pinned-actions guard OK: every GitHub Action is SHA-pinned"