Skip to content

Commit 5ab9024

Browse files
VijitSingh97claude
andcommitted
ci: supply-chain & secrets hardening (closes #11)
The workflows were the least-hardened surface in the org (this repo ships no app dependencies). Bring them in line with pithead/rigforge. - Pin all GitHub Actions by commit SHA with `# vX.Y.Z` comments (checkout v6.0.3, configure-pages v6.0.0, upload-pages-artifact v5.0.0, deploy-pages v5.0.0); add `persist-credentials: false` on checkouts. - deploy.yml: scope permissions per-job — the build job is read-only; only the deploy job gets pages:write + id-token:write (was repo-wide). Pass the Pages base_url via env instead of inline expansion (template-injection). - .github/dependabot.yml: track the github-actions ecosystem (weekly, grouped) to keep the SHA pins current. - .github/workflows/security.yml: gitleaks full-history secret scan + zizmor workflow audit (online advisory cross-check), mirroring rigforge. - .pre-commit-config.yaml: gitleaks hook pinned in lockstep with CI; document it in CONTRIBUTING under "Secret scanning". Verified locally: gitleaks reports no leaks across history; zizmor reports no findings on the workflows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 789df88 commit 5ab9024

6 files changed

Lines changed: 138 additions & 8 deletions

File tree

.github/dependabot.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Keep the SHA-pinned GitHub Actions current. Dependabot bumps the commit pin AND the trailing
2+
# "# vX.Y.Z" comment together, and opens PRs for any security advisories affecting an action we use.
3+
#
4+
# Scope is github-actions ONLY (#11): this repo is a static Hugo site with no app dependencies —
5+
# no package.json, go.mod, pip, or docker to track. Hugo itself is installed by version-pinned
6+
# `wget` of a release .deb in the workflows (HUGO_VERSION), which Dependabot cannot see, so Hugo
7+
# bumps stay manual (see #15).
8+
version: 2
9+
updates:
10+
- package-ecosystem: "github-actions"
11+
directory: "/" # github-actions ecosystem watches .github/workflows/
12+
schedule:
13+
interval: "weekly"
14+
commit-message:
15+
prefix: "ci" # -> "ci(deps): bump actions/checkout ..."
16+
include: "scope"
17+
labels:
18+
- "infra"
19+
groups:
20+
# One rollup PR for all action bumps rather than one-per-action — low-noise for a repo this small.
21+
github-actions:
22+
patterns:
23+
- "*"

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ jobs:
2525
HTMLTEST_VERSION: "0.17.0"
2626
steps:
2727
- name: Checkout
28-
uses: actions/checkout@v6
28+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
2929
with:
3030
fetch-depth: 0
31+
persist-credentials: false # zizmor: artipacked
3132

3233
- name: Install Hugo (extended)
3334
run: |

.github/workflows/deploy.yml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ on:
99
- cron: "17 6 * * *"
1010
workflow_dispatch:
1111

12+
# Least-privilege floor: the build job only reads the tree. Only the deploy job
13+
# needs pages:write + id-token:write, scoped to it below. (zizmor: excessive-permissions)
1214
permissions:
1315
contents: read
14-
pages: write
15-
id-token: write
1616

1717
# Allow one concurrent deployment; don't cancel an in-progress production deploy.
1818
concurrency:
@@ -35,13 +35,14 @@ jobs:
3535
https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
3636
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
3737
- name: Checkout
38-
uses: actions/checkout@v6
38+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
3939
with:
4040
submodules: recursive
4141
fetch-depth: 0
42+
persist-credentials: false # zizmor: artipacked
4243
- name: Setup Pages
4344
id: pages
44-
uses: actions/configure-pages@v6
45+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
4546
with:
4647
enablement: true
4748
- name: Refresh release versions (best-effort)
@@ -52,13 +53,16 @@ jobs:
5253
env:
5354
HUGO_ENVIRONMENT: production
5455
TZ: UTC
56+
# Pass the Pages URL through env rather than expanding it inline in the
57+
# script (avoids template-injection; zizmor: template-injection).
58+
BASE_URL: ${{ steps.pages.outputs.base_url }}
5559
run: |
5660
hugo \
5761
--gc \
5862
--minify \
59-
--baseURL "${{ steps.pages.outputs.base_url }}/"
63+
--baseURL "${BASE_URL}/"
6064
- name: Upload artifact
61-
uses: actions/upload-pages-artifact@v5
65+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
6266
with:
6367
path: ./public
6468

@@ -68,7 +72,11 @@ jobs:
6872
url: ${{ steps.deployment.outputs.page_url }}
6973
runs-on: ubuntu-latest
7074
needs: build
75+
# Only this job touches Pages — grant the write scopes here, not repo-wide.
76+
permissions:
77+
pages: write
78+
id-token: write
7179
steps:
7280
- name: Deploy to GitHub Pages
7381
id: deployment
74-
uses: actions/deploy-pages@v5
82+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0

.github/workflows/security.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Security
2+
3+
# Supply-chain & secrets gates (#11):
4+
# - gitleaks: scan the full git history for committed secrets (tokens, credentials) on every
5+
# push and PR.
6+
# - zizmor: static-audit the GitHub Actions workflows themselves (template injection, over-broad
7+
# GITHUB_TOKEN, unpinned actions, credential persistence) AND cross-reference the actions we
8+
# pin against the GitHub Advisory Database (online audit).
9+
# Dependabot (github-actions) lives in .github/dependabot.yml; the matching gitleaks pre-commit
10+
# hook lives in .pre-commit-config.yaml.
11+
12+
on:
13+
push:
14+
branches: [main]
15+
pull_request:
16+
# Re-audit on a schedule so a newly-published advisory against an action we pin trips the gate
17+
# even during quiet periods with no pushes — the online zizmor audit is time-varying by design.
18+
schedule:
19+
- cron: "0 7 * * 1" # Mondays 07:00 UTC
20+
21+
# Both jobs only read the tree to scan it. Pin the floor to read-only (zizmor: excessive-permissions).
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
gitleaks:
27+
name: Secret scan (gitleaks)
28+
runs-on: ubuntu-24.04
29+
# The weekly tick exists for zizmor's advisory re-audit; history doesn't change between pushes,
30+
# so there's nothing new for gitleaks to scan on a schedule.
31+
if: github.event_name != 'schedule'
32+
env:
33+
# Pinned + checksum-verified — reproducible and immune to runner-image drift. Keep
34+
# GITLEAKS_VERSION in lockstep with .pre-commit-config.yaml.
35+
GITLEAKS_VERSION: "8.30.1"
36+
GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
37+
steps:
38+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
39+
with:
40+
fetch-depth: 0 # scan EVERY commit, not just the tip — a secret is still a leak once pushed
41+
persist-credentials: false # zizmor: artipacked
42+
- name: Install pinned gitleaks
43+
run: |
44+
set -euo pipefail
45+
tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
46+
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${tarball}" -o "$tarball"
47+
echo "${GITLEAKS_SHA256} ${tarball}" | sha256sum -c -
48+
tar -xzf "$tarball" gitleaks
49+
sudo install gitleaks /usr/local/bin/gitleaks
50+
gitleaks version
51+
# Full-history scan with the built-in ruleset. --redact keeps any match out of the public logs;
52+
# the job still fails (non-zero exit) so a leak blocks the merge.
53+
- name: Scan git history for secrets
54+
run: gitleaks git . --redact --no-banner --verbose
55+
56+
zizmor:
57+
name: Workflow audit (zizmor)
58+
runs-on: ubuntu-24.04
59+
env:
60+
ZIZMOR_VERSION: "1.25.2"
61+
steps:
62+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
63+
with:
64+
persist-credentials: false # zizmor: artipacked
65+
# pipx is preinstalled on ubuntu-24.04.
66+
- name: Install pinned zizmor
67+
run: pipx install "zizmor==${ZIZMOR_VERSION}"
68+
# Online audits ON (zizmor's default): GH_TOKEN lets the `known-vulnerable-actions` audit query
69+
# the GitHub Advisory Database, so a CVE disclosed against an action we pin fails the gate. The
70+
# built-in token (read-only here) is enough — advisory data is public; it's only for API access.
71+
# This complements Dependabot: zizmor blocks the merge, Dependabot opens the bump.
72+
- name: Audit GitHub Actions workflows
73+
env:
74+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75+
run: zizmor .github/workflows/

.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Pre-commit hooks for the site. Install once per clone:
2+
#
3+
# pipx install pre-commit # or: pip install pre-commit
4+
# pre-commit install
5+
#
6+
# Scope here is the secret-scanning gate from #11: gitleaks, pinned to the SAME version CI runs
7+
# (.github/workflows/security.yml) so a leak is caught locally before it's ever pushed.
8+
repos:
9+
- repo: https://github.com/gitleaks/gitleaks
10+
rev: v8.30.1 # keep in lockstep with GITLEAKS_VERSION in .github/workflows/security.yml
11+
hooks:
12+
- id: gitleaks

CONTRIBUTING.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ not in the templates. See the [README](README.md#where-the-content-lives) for th
3434
```
3535
4. Update the README or other docs for any user-facing change.
3636

37+
## Secret scanning
38+
39+
CI runs [gitleaks](https://github.com/gitleaks/gitleaks) over the full history on every push and PR,
40+
so an accidentally committed token or credential blocks the merge. Catch it locally first by
41+
installing the pre-commit hook (it runs the same pinned gitleaks on staged changes):
42+
43+
```bash
44+
pipx install pre-commit # or: pip install pre-commit
45+
pre-commit install
46+
```
47+
3748
## Opening a pull request
3849

3950
- Target the `main` branch and fill out the PR template.

0 commit comments

Comments
 (0)