Skip to content

Commit a6c2c5c

Browse files
VijitSingh97claude
andauthored
ci: supply-chain hardening (gitleaks, Dependabot, zizmor) + develop/main branch model (#120)
* ci: supply-chain & secrets hardening — gitleaks, Dependabot, zizmor (#117) Adds the three cross-cutting gates from #117 on top of RigForge's existing SHA-pinned actions and commit-verified XMRig build. Scope is only the gaps, not a redo of that work. - gitleaks: new Security workflow scans the full git history for committed secrets (pool creds, tokens, the #113 stratum access-password) on every push and PR, with a matching pre-commit hook. Binary is version+checksum pinned, mirroring the shellcheck/shfmt installs. - Dependabot: .github/dependabot.yml for the github-actions ecosystem only (RigForge has no pip/npm/docker deps) — keeps the action pins current and surfaces advisories. - zizmor: audits the workflows for template injection, over-broad GITHUB_TOKEN, and credential persistence. Hardened ci.yml/release.yml to a read-only default token and persist-credentials: false on checkout so the audit is clean. The broader pre-commit hook set (shellcheck/shfmt/yamllint/markdownlint + freebies) is left to #118, which extends .pre-commit-config.yaml. Validated locally: gitleaks clean over full history, zizmor reports no findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(security): enable zizmor online audits + weekly schedule Run zizmor with online audits on (its default) so the known-vulnerable-actions audit cross-references the actions we pin against the GitHub Advisory Database — a CVE disclosed against a pinned action now fails the gate, not just structural issues. GH_TOKEN is the built-in read-only token (advisory data is public; it's only for API access). Add a weekly schedule so a freshly-published advisory trips against main even with no open PRs (the online audit is time-varying by design). gitleaks is skipped on the scheduled tick — history doesn't change between pushes. Complements Dependabot: zizmor blocks the merge, Dependabot opens the bump. Validated: zizmor online reports no findings against the current pins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: adopt the develop/main branch model (mirrors Pithead) Pithead uses develop as the default integration branch and main as the release branch; mirror that here (develop is now the repo default). - ci.yml + security.yml: run on push to [main, develop] (PRs already run on any base via the unfiltered pull_request trigger). - coverage: diff-cover now compares against the PR's actual base branch (github.base_ref, falling back to the pushed branch) instead of a hardcoded origin/main — so patch coverage stays correct once develop diverges from main. - CONTRIBUTING: document the two-branch model; PRs target develop. - RELEASING: build the release commit on develop, fast-forward into main, tag from main. develop is merged to main at each release (1.1 onward). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7bc5a5c commit a6c2c5c

9 files changed

Lines changed: 199 additions & 9 deletions

File tree

.github/dependabot.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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 (#117): RigForge is pure shell — no pip, npm, or docker ecosystems
5+
# to track. The XMRig source is pinned by version+commit and verified at build time, not via a
6+
# package manager, so it isn't a Dependabot ecosystem either.
7+
version: 2
8+
updates:
9+
- package-ecosystem: "github-actions"
10+
directory: "/" # github-actions ecosystem watches .github/workflows/
11+
schedule:
12+
interval: "weekly"
13+
commit-message:
14+
prefix: "ci" # -> "ci(deps): bump actions/checkout ..."
15+
include: "scope"
16+
labels:
17+
- "infra"
18+
groups:
19+
# One rollup PR for all action bumps rather than one-per-action — low-noise for a repo this small.
20+
github-actions:
21+
patterns:
22+
- "*"

.github/workflows/ci.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ name: CI
22

33
on:
44
push:
5-
branches: [main]
5+
branches: [main, develop] # develop is the integration branch; main is the release branch
66
pull_request:
77

8+
# Least privilege: every job here only reads the repo to lint/test/build — nothing publishes or
9+
# writes. Set the floor once at the top so all jobs inherit a read-only GITHUB_TOKEN (zizmor:
10+
# excessive-permissions).
11+
permissions:
12+
contents: read
13+
814
jobs:
915
lint:
1016
name: Lint (shellcheck + shfmt)
@@ -16,6 +22,8 @@ jobs:
1622
SHFMT_SHA256: "fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1"
1723
steps:
1824
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
25+
with:
26+
persist-credentials: false # no pushes from CI; don't leave the token in .git/config (zizmor: artipacked)
1927
# Install PINNED, checksum-verified shellcheck + shfmt instead of the runner's preinstalled
2028
# builds, so lint/format results are reproducible and don't drift with the runner image (#6).
2129
# Downloaded straight from the upstream releases — no `apt-get update`, avoiding the mirror
@@ -46,6 +54,8 @@ jobs:
4654
runs-on: ubuntu-24.04
4755
steps:
4856
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
57+
with:
58+
persist-credentials: false # zizmor: artipacked
4959
# Ubuntu is RigForge's supported target. The suite still exercises the macOS code path here via
5060
# stubs (OS_TYPE=Darwin + faked sysctl), so the macOS config profile is covered without a Mac
5161
# runner; contributors on macOS can also run `make test` locally. jq is preinstalled on the runner.
@@ -57,6 +67,8 @@ jobs:
5767
runs-on: macos-14 # Apple silicon; pinned (not macos-latest) to avoid image drift
5868
steps:
5969
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
70+
with:
71+
persist-credentials: false # zizmor: artipacked
6072
# The Linux job only SIMULATES macOS (STUB_UNAME_S=Darwin + stubbed sed/launchctl). Here the suite
6173
# runs on real macOS, natively exercising the Darwin paths — BSD `sed` (the donate.h patch), the
6274
# macOS config profile, `mac_*` process control + the launchd login agent, and BSD `tar`/`date`
@@ -79,6 +91,8 @@ jobs:
7991
runs-on: ubuntu-24.04
8092
steps:
8193
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
94+
with:
95+
persist-credentials: false # zizmor: artipacked
8296
# Runs the real script end-to-end inside a disposable Ubuntu container (RigForge's documented
8397
# Linux target), exercising the genuine Linux deploy path and /etc idempotency with real tools.
8498
- name: Run end-to-end suite
@@ -91,12 +105,16 @@ jobs:
91105
# Pin diff-cover (the patch-coverage gate); kcov + jq are pinned inside tests/coverage.sh.
92106
DIFF_COVER_VERSION: "10.3.0"
93107
PATCH_COVERAGE_MIN: "90" # new/changed lines in rigforge.sh + util must be this % covered
108+
# Diff the PR against the branch it targets (develop or main). On a push, github.base_ref is empty,
109+
# so fall back to the pushed branch itself — that's an empty diff that trivially passes.
110+
BASE_REF: ${{ github.base_ref || github.ref_name }}
94111
steps:
95112
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
96113
with:
97-
fetch-depth: 0 # diff-cover needs history to diff the PR against origin/main
114+
fetch-depth: 0 # diff-cover needs history to diff the PR against its base branch
115+
persist-credentials: false # the fetch below is read-only on a public repo (zizmor: artipacked)
98116
- name: Fetch base branch for diff-cover
99-
run: git fetch --no-tags origin main
117+
run: git fetch --no-tags origin "$BASE_REF"
100118
# Runs tests/run.sh under a digest-pinned kcov image, reports rigforge.sh + util/proposed-grub.sh
101119
# coverage, and fails if the total drops below the committed floor (tests/coverage-floor.txt).
102120
- name: Measure coverage (kcov) + enforce the total floor
@@ -106,4 +124,4 @@ jobs:
106124
- name: Install pinned diff-cover
107125
run: pipx install "diff-cover==${DIFF_COVER_VERSION}"
108126
- name: Enforce patch coverage on changed lines
109-
run: diff-cover coverage/cobertura.xml --compare-branch=origin/main --fail-under="${PATCH_COVERAGE_MIN}"
127+
run: diff-cover coverage/cobertura.xml --compare-branch="origin/$BASE_REF" --fail-under="${PATCH_COVERAGE_MIN}"

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ jobs:
2222
TAG: ${{ github.ref_name }}
2323
steps:
2424
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
25+
with:
26+
# Releasing goes through `gh` with GH_TOKEN below, not `git push`, so the checkout never
27+
# needs the token left in .git/config (zizmor: artipacked).
28+
persist-credentials: false
2529

2630
- name: Verify the tag matches VERSION
2731
run: |

.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 (#117):
4+
# - gitleaks: scan the full git history for committed secrets (pool creds, the stratum
5+
# access-password from #113, tokens) on every push and PR.
6+
# - zizmor: static-audit the GitHub Actions workflows themselves (template injection,
7+
# over-broad GITHUB_TOKEN, unpinned actions, credential persistence) AND cross-reference the
8+
# actions we 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, develop] # develop is the integration branch; main is the release branch
15+
pull_request:
16+
# Re-audit main 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, so
30+
# there's nothing new for gitleaks to scan on a schedule.
31+
if: github.event_name != 'schedule'
32+
env:
33+
# Pinned + checksum-verified, same as the shellcheck/shfmt installs in ci.yml — reproducible and
34+
# immune to runner-image drift. Keep 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; same install path as diff-cover in ci.yml's coverage job.
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# RigForge pre-commit hooks. 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 #117: gitleaks, pinned to the SAME version CI runs
7+
# (.github/workflows/security.yml) so local catches a leak before it's ever pushed. The broader
8+
# hook set — shellcheck, shfmt, yamllint, markdownlint, and the freebies — is tracked in #118 and
9+
# will be added to this file, keeping the Makefile's SHELL_FILES the source of truth.
10+
repos:
11+
- repo: https://github.com/gitleaks/gitleaks
12+
rev: v8.30.1 # keep in lockstep with GITLEAKS_VERSION in .github/workflows/security.yml
13+
hooks:
14+
- id: gitleaks

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ All notable changes to RigForge are documented here. The format is based on
77

88
## [Unreleased]
99

10+
### Added
11+
- **Supply-chain & secret-scanning CI gates (#117).** Three cross-cutting hardening gates on top of
12+
the existing SHA-pinned actions and commit-verified XMRig build:
13+
- **gitleaks** — a new `Security` workflow scans the full git history for committed secrets (pool
14+
credentials, tokens, the stratum access-password) on every push and PR, plus a matching
15+
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml) hook so a leak is caught before it's pushed.
16+
The binary is version- and checksum-pinned, like the existing shellcheck/shfmt installs.
17+
- **Dependabot** ([`.github/dependabot.yml`](./.github/dependabot.yml)) — keeps the hand-pinned
18+
GitHub Actions current (`github-actions` ecosystem only; RigForge has no pip/npm/docker deps) and
19+
surfaces action security advisories.
20+
- **zizmor** — static-audits the workflows for template injection, over-broad `GITHUB_TOKEN`, and
21+
credential persistence, and (online) cross-references the actions we pin against the GitHub
22+
Advisory Database. Runs on push/PR plus a weekly schedule, so a CVE disclosed against a pinned
23+
action trips the gate even with no open PRs. Hardened the existing `ci.yml`/`release.yml` to a
24+
read-only default token and `persist-credentials: false` on checkout to make the audit clean.
25+
1026
## [1.0.1] - 2026-06-13
1127

1228
### Fixed

CONTRIBUTING.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,30 @@ RigForge is portable Bash that has to run on Ubuntu/Debian and macOS, so:
3131
`shfmt` formatting.)
3232
- Update the README or other docs when you change behaviour or add options.
3333

34+
## Secret scanning
35+
36+
CI runs [gitleaks](https://github.com/gitleaks/gitleaks) over the full history on every push and PR,
37+
so an accidentally committed token or pool credential blocks the merge. Catch it locally first by
38+
installing the pre-commit hook (it runs the same pinned gitleaks on staged changes):
39+
40+
```bash
41+
pipx install pre-commit # or: pip install pre-commit
42+
pre-commit install
43+
```
44+
45+
## Branching
46+
47+
RigForge uses a two-branch model (same as [Pithead](https://github.com/p2pool-starter-stack/pithead)):
48+
49+
- **`develop`** — the default, integration branch. All PRs target `develop`.
50+
- **`main`** — the release branch. `develop` is merged into `main` at each release, and version tags
51+
are cut from `main`.
52+
3453
## Submitting a pull request
3554

36-
1. Fork the repo and create a topic branch off `main`.
55+
1. Fork the repo and create a topic branch off `develop`.
3756
2. Make your change and confirm `shellcheck` passes.
38-
3. Open a PR against `main` and fill out the template.
57+
3. Open a PR against `develop` and fill out the template.
3958
4. **All PRs require review** before merging — a code owner will take a look.
4059

4160
Keep PRs focused and the description clear about *what* changed and *why*. Small,

RELEASING.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ From `1.0.0` on, the `config.json` and CLI surface is stable — a breaking chan
1414

1515
## Cutting a release
1616

17-
1. Ensure `main` is green: `make test` (and `make test-e2e` if Docker is available).
17+
Work lands on **`develop`** (the integration branch); a release is the point where `develop` is
18+
promoted to **`main`** and tagged. The steps below build the release commit on `develop`, merge it to
19+
`main`, and tag from `main`.
20+
21+
1. Ensure `develop` is green: `make test` (and `make test-e2e` if Docker is available).
1822
2. **Full real-hardware e2e (the release gate).** CI exercises everything it can (lint, the
1923
dependency-free suite, the Docker `/etc` e2e, the coverage gate) — but it can't compile XMRig,
2024
reserve HugePages, write MSRs, set the governor, or actually hash. So on a **real Linux rig**, run
@@ -39,11 +43,16 @@ From `1.0.0` on, the `config.json` and CLI surface is stable — a breaking chan
3943
3. In [`CHANGELOG.md`](./CHANGELOG.md), move the `## [Unreleased]` entries under a new
4044
`## [X.Y.Z] - YYYY-MM-DD` heading, then leave a fresh empty `## [Unreleased]` above it.
4145
4. Bump [`VERSION`](./VERSION) to `X.Y.Z`.
42-
5. Commit the two together:
46+
5. Commit the two together on `develop`:
4347
```bash
4448
git commit -am "release: vX.Y.Z"
49+
git push origin develop
50+
```
51+
6. Promote `develop` to `main`:
52+
```bash
53+
git checkout main && git merge --ff-only develop && git push origin main
4554
```
46-
6. Tag and push (annotated tag, **matching `VERSION`**):
55+
7. Tag and push from `main` (annotated tag, **matching `VERSION`**):
4756
```bash
4857
git tag -a vX.Y.Z -m "RigForge vX.Y.Z"
4958
git push origin main --follow-tags

SECURITY.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ sudo ufw deny 8080/tcp # block it outright …
3737
sudo ufw allow from <DASHBOARD_IP> to any port 8080 proto tcp # … or scope it to one host
3838
```
3939

40+
## Supply chain & secret scanning
41+
42+
RigForge is built to be reproducible and tamper-evident:
43+
44+
- **Pinned, verified inputs.** XMRig is cloned at a pinned commit and verified against a hardcoded
45+
hash before it builds; GitHub Actions are SHA-pinned; CI tool installs (shellcheck, shfmt, gitleaks)
46+
are version- and checksum-verified. **Dependabot** keeps the action pins current and flags advisories.
47+
- **Secret scanning.** [gitleaks](https://github.com/gitleaks/gitleaks) scans the full git history on
48+
every push and PR, and runs as a pre-commit hook, so credentials can't slip into the repo.
49+
- **Workflow auditing.** [zizmor](https://github.com/zizmorcore/zizmor) static-audits the CI workflows
50+
for template injection, over-broad token scopes, and credential persistence; jobs run with a
51+
least-privilege, read-only `GITHUB_TOKEN` by default.
52+
4053
## Supported versions
4154

4255
Only the latest `main` is supported. Please reproduce against current `main`

0 commit comments

Comments
 (0)