mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:21:31 +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:
+23
-12
@@ -14,10 +14,10 @@ jobs:
|
|||||||
name: Go Build & Test
|
name: Go Build & Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.10'
|
go-version: '1.25.10'
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
run: bash scripts/check-coverage-thresholds.sh
|
run: bash scripts/check-coverage-thresholds.sh
|
||||||
|
|
||||||
- name: Upload Coverage Report
|
- name: Upload Coverage Report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: go-coverage
|
name: go-coverage
|
||||||
path: coverage.out
|
path: coverage.out
|
||||||
@@ -188,7 +188,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: go-build-and-test
|
needs: go-build-and-test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Show Docker versions
|
- name: Show Docker versions
|
||||||
run: |
|
run: |
|
||||||
@@ -328,10 +328,10 @@ jobs:
|
|||||||
name: Frontend Build
|
name: Frontend Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
@@ -339,6 +339,17 @@ jobs:
|
|||||||
working-directory: web
|
working-directory: web
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: npm audit (production deps, high+critical)
|
||||||
|
# Phase 1 TEST-L2 closure (2026-05-13):
|
||||||
|
# Production frontend dependencies must not carry high or
|
||||||
|
# critical CVEs. Dev-only deps (vitest, vite, eslint, etc.)
|
||||||
|
# are excluded via --omit=dev since they never ship to
|
||||||
|
# operators. If this gate fires, triage each finding via npm
|
||||||
|
# overrides, dep upgrade, or a tracked --ignore with an issue
|
||||||
|
# link. Do not mass-silence findings.
|
||||||
|
working-directory: web
|
||||||
|
run: npm audit --omit=dev --audit-level=high
|
||||||
|
|
||||||
- name: TypeScript Check
|
- name: TypeScript Check
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: npx tsc --noEmit
|
run: npx tsc --noEmit
|
||||||
@@ -374,10 +385,10 @@ jobs:
|
|||||||
name: Helm Chart Validation
|
name: Helm Chart Validation
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Install Helm
|
- name: Install Helm
|
||||||
uses: azure/setup-helm@v4
|
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||||
with:
|
with:
|
||||||
version: '3.13.0'
|
version: '3.13.0'
|
||||||
|
|
||||||
@@ -527,10 +538,10 @@ jobs:
|
|||||||
needs: [go-build-and-test]
|
needs: [go-build-and-test]
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.10'
|
go-version: '1.25.10'
|
||||||
cache: true
|
cache: true
|
||||||
@@ -624,10 +635,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25.10'
|
go-version: '1.25.10'
|
||||||
cache: true
|
cache: true
|
||||||
|
|||||||
@@ -53,17 +53,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
if: matrix.language == 'go'
|
if: matrix.language == 'go'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
# Match ci.yml + release.yml + security-deep-scan.yml.
|
# Match ci.yml + release.yml + security-deep-scan.yml.
|
||||||
go-version: '1.25.10'
|
go-version: '1.25.10'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# Use the security-and-quality query suite — security finds plus
|
# Use the security-and-quality query suite — security finds plus
|
||||||
@@ -72,10 +72,10 @@ jobs:
|
|||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{ matrix.language }}"
|
category: "/language:${{ matrix.language }}"
|
||||||
# SARIF upload is implicit (and is what populates the Security tab).
|
# SARIF upload is implicit (and is what populates the Security tab).
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
# The compose stack builds the certctl image from the repo
|
# The compose stack builds the certctl image from the repo
|
||||||
# root Dockerfile. Buildx gives the build a usable cache and
|
# root Dockerfile. Buildx gives the build a usable cache and
|
||||||
# works with newer compose versions.
|
# works with newer compose versions.
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||||
|
|
||||||
- name: Run loadtest
|
- name: Run loadtest
|
||||||
run: make loadtest
|
run: make loadtest
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# authoritative machine-readable form; summary.txt is the
|
# authoritative machine-readable form; summary.txt is the
|
||||||
# human-readable text the README baseline tracks.
|
# human-readable text the README baseline tracks.
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: k6-summary-${{ github.run_id }}
|
name: k6-summary-${{ github.run_id }}
|
||||||
path: deploy/test/loadtest/results/
|
path: deploy/test/loadtest/results/
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ jobs:
|
|||||||
os: [linux, darwin]
|
os: [linux, darwin]
|
||||||
arch: [amd64, arm64]
|
arch: [amd64, arm64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
cat "${OUTPUT_NAME}.sha256"
|
cat "${OUTPUT_NAME}.sha256"
|
||||||
|
|
||||||
- name: Upload build artefacts
|
- name: Upload build artefacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ steps.build.outputs.output_name }}
|
name: binary-${{ steps.build.outputs.output_name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -151,7 +151,7 @@ jobs:
|
|||||||
hashes: ${{ steps.hashes.outputs.hashes }}
|
hashes: ${{ steps.hashes.outputs.hashes }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download binary artefacts
|
- name: Download binary artefacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
pattern: binary-*
|
pattern: binary-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
checksums.txt
|
checksums.txt
|
||||||
|
|
||||||
- name: Upload artefacts to GitHub Release
|
- name: Upload artefacts to GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
@@ -212,7 +212,7 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
|
||||||
with:
|
with:
|
||||||
base64-subjects: "${{ needs.aggregate-checksums.outputs.hashes }}"
|
base64-subjects: "${{ needs.aggregate-checksums.outputs.hashes }}"
|
||||||
upload-assets: true
|
upload-assets: true
|
||||||
@@ -235,10 +235,10 @@ jobs:
|
|||||||
id-token: write # Cosign keyless OIDC identity token
|
id-token: write # Cosign keyless OIDC identity token
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -249,14 +249,14 @@ jobs:
|
|||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||||
|
|
||||||
- name: Build and push server image
|
- name: Build and push server image
|
||||||
id: server-push
|
id: server-push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
@@ -291,7 +291,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push agent image
|
- name: Build and push agent image
|
||||||
id: agent-push
|
id: agent-push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile.agent
|
file: ./Dockerfile.agent
|
||||||
@@ -334,7 +334,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Extract version from tag
|
- name: Extract version from tag
|
||||||
id: version
|
id: version
|
||||||
@@ -351,7 +351,7 @@ jobs:
|
|||||||
# README is the source of truth for those, and inlining them in every
|
# README is the source of truth for those, and inlining them in every
|
||||||
# release page produces the kind of "every release looks identical"
|
# release page produces the kind of "every release looks identical"
|
||||||
# noise that gives operators no signal about what actually changed.
|
# noise that gives operators no signal about what actually changed.
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
||||||
with:
|
with:
|
||||||
# Pin the release title to the tag name. softprops/action-gh-release@v2
|
# Pin the release title to the tag name. softprops/action-gh-release@v2
|
||||||
# falls back to the most recent commit subject when `name:` is omitted,
|
# falls back to the most recent commit subject when `name:` is omitted,
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.25'
|
go-version: '1.25'
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: ZAP baseline
|
- name: ZAP baseline
|
||||||
uses: zaproxy/action-baseline@v0.10.0
|
uses: zaproxy/action-baseline@1e1871e84428617b969d4a1f981a8255630d54b0 # v0.10.0
|
||||||
with:
|
with:
|
||||||
target: 'https://localhost:8443'
|
target: 'https://localhost:8443'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -175,7 +175,7 @@ jobs:
|
|||||||
# --- Upload everything as artefacts ---
|
# --- Upload everything as artefacts ---
|
||||||
|
|
||||||
- name: Upload deep-scan receipts
|
- name: Upload deep-scan receipts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: security-deep-scan-${{ github.run_id }}
|
name: security-deep-scan-${{ github.run_id }}
|
||||||
|
|||||||
@@ -88,3 +88,8 @@ Thumbs.db
|
|||||||
# CERTCTL_TEST_CA_BUNDLE=./certs/ca.crt. Material is regenerated on every
|
# CERTCTL_TEST_CA_BUNDLE=./certs/ca.crt. Material is regenerated on every
|
||||||
# `docker compose up` and never belongs in git.
|
# `docker compose up` and never belongs in git.
|
||||||
/deploy/test/certs/
|
/deploy/test/certs/
|
||||||
|
|
||||||
|
# Phase 1 RED-1 closure (2026-05-13): the f5-mock-icontrol Dockerfile
|
||||||
|
# rebuilds from source via multi-stage build (deploy/test/f5-mock-icontrol/
|
||||||
|
# Dockerfile line 13). The compiled ELF must not be tracked.
|
||||||
|
deploy/test/f5-mock-icontrol/f5-mock-icontrol
|
||||||
|
|||||||
Binary file not shown.
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