mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:31:29 +00:00
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
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/ci-guards/no-precompiled-binary.sh
|
||||
#
|
||||
# Phase 1 RED-1 closure (2026-05-13): no precompiled binary (ELF /
|
||||
# Mach-O / PE) should ever be tracked in the repo. The original
|
||||
# Phase 1 trigger was `deploy/test/f5-mock-icontrol/f5-mock-icontrol`
|
||||
# — an 8.6 MB ARM64 ELF that lived in git alongside the Go source
|
||||
# that builds it. The Dockerfile for that fixture already runs
|
||||
# `go build` from source inside the container, so the tracked
|
||||
# binary was vestigial. Deleting it cost nothing; tracking it cost
|
||||
# 8.6 MB per clone forever.
|
||||
#
|
||||
# This guard scans every TRACKED file (i.e. what an external clone
|
||||
# sees, not what's in the operator's working tree) and uses `file(1)`
|
||||
# to detect compiled executables. Any hit fails the build with a
|
||||
# clear pointer to the right fix.
|
||||
#
|
||||
# Allowlist:
|
||||
# - PNG / JPG / PDF / SVG / GIF / WebP — image assets are not
|
||||
# binaries in the supply-chain sense even though `file` reports
|
||||
# them as "executable" in some encodings.
|
||||
# - Specific large fixtures that legitimately need to be tracked
|
||||
# (e.g. canonical certificate test vectors). Add to the
|
||||
# allowlist with a rationale comment.
|
||||
#
|
||||
# Mirror of the B6-no-private-keys-in-tree.sh pattern.
|
||||
|
||||
set -e
|
||||
|
||||
# What `file(1)` outputs we treat as a binary smell. ELF / Mach-O /
|
||||
# PE / Java class / WebAssembly all qualify. Image / archive / text
|
||||
# formats do NOT.
|
||||
BAD_TYPES='ELF|Mach-O|PE32|PE32\+|compiled Java class|WebAssembly'
|
||||
|
||||
VIOLATIONS=$(git ls-files -z \
|
||||
| xargs -0 file --brief --separator='|' --print0 2>/dev/null \
|
||||
| tr '\0' '\n' \
|
||||
| grep -E "$BAD_TYPES" \
|
||||
| grep -vE '^$' \
|
||||
|| true)
|
||||
|
||||
if [ -n "$VIOLATIONS" ]; then
|
||||
echo "::error::no-precompiled-binary regression: tracked executable file(s) found:"
|
||||
echo ""
|
||||
echo "$VIOLATIONS"
|
||||
echo ""
|
||||
echo "Precompiled binaries must not be tracked. If this is a test"
|
||||
echo "fixture, route the build through a Dockerfile / Makefile target"
|
||||
echo "that rebuilds from source. If it is a legitimate exception,"
|
||||
echo "add the path to the allowlist in scripts/ci-guards/no-precompiled-binary.sh"
|
||||
echo "with a rationale comment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "no-precompiled-binary guard OK: no tracked ELF / Mach-O / PE / class / wasm binaries"
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user