Skip to content

Commit 1cc060f

Browse files
authored
ci: add advisory scan lanes (actionlint, zizmor, gitleaks) + make lint-ci (#64)
A dedicated scans.yml (PR + main push + weekly cron) runs three log-only advisory jobs, mirroring the podman lane's model: continue-on-error and kept out of the ruleset's required checks. actionlint via rhysd's digest-pinned image; zizmor and gitleaks via first-party actions, SHA- and CLI-version-pinned, SARIF/comments disabled so the workflow needs only contents: read. make lint-ci mirrors the actionlint image locally. verify-ci-supplychain-unit.bats gains advisory/read-only guards plus a fleet-wide check that every workflow uses: stays SHA-pinned.
1 parent b5f6399 commit 1cc060f

4 files changed

Lines changed: 85 additions & 1 deletion

File tree

.github/workflows/scans.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Advisory scanners: workflow linting (actionlint), workflow security posture (zizmor), and a
2+
# full-history secret sweep (gitleaks). Advisory = continue-on-error + NOT in the branch ruleset's
3+
# required checks - do NOT add these job names to the ruleset. Log-only by design: no SARIF upload,
4+
# no PR comments, so the whole workflow stays contents: read.
5+
name: advisory scans
6+
7+
on:
8+
pull_request:
9+
push:
10+
branches: [main]
11+
schedule:
12+
- cron: "0 6 * * 1" # weekly: scanner rules move even when the repo doesn't
13+
14+
permissions:
15+
contents: read # least privilege: log-only advisory
16+
17+
jobs:
18+
actionlint:
19+
name: actionlint (advisory)
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 5
22+
continue-on-error: true
23+
steps:
24+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25+
- name: Lint workflows (digest-pinned image; same ref as make lint-ci)
26+
run: docker run --rm --platform linux/amd64 -v "$PWD:/repo" --workdir /repo rhysd/actionlint:1.7.12@sha256:9d36088643581e728c969f35141f88139fec77280b2be23c1f66f8e40e1025e7 -color
27+
28+
zizmor:
29+
name: zizmor (advisory)
30+
runs-on: ubuntu-latest
31+
timeout-minutes: 5
32+
continue-on-error: true
33+
steps:
34+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
35+
- uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
36+
with:
37+
version: "1.26.1" # pin the CLI too, not just the action
38+
advanced-security: false # log-only: no SARIF upload (needs a write grant to the security tab)
39+
40+
gitleaks:
41+
name: gitleaks (advisory)
42+
runs-on: ubuntu-latest
43+
timeout-minutes: 10
44+
continue-on-error: true
45+
steps:
46+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
47+
with: { fetch-depth: 0 } # full history - a leak in any past commit counts
48+
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
49+
env:
50+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51+
GITLEAKS_ENABLE_COMMENTS: "false" # log-only: PR comments would need pull-requests + write

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The suites are [bats-core](https://github.com/bats-core/bats-core), vendored as
3131
make test # gate: CLI/init/install units + egress + security invariants (needs docker/podman)
3232
make test-nightly # heavy suites: lock, learn, runtimes, nix, agents, control-plane
3333
make structure # base-image invariants (no sudo, uid 1000, firewall packages) via container-structure-test
34+
make lint-ci # advisory: actionlint over .github/workflows (mirrors the scans.yml lane)
3435
```
3536

3637
Each suite is `test/<name>.bats` (gate) or `test/nightly-<name>.bats` (heavy); shared helpers live in

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ ENGINE_BATS := $(ACCEPT_BATS) $(SECURITY_BATS)
2525
GATE_BATS := $(UNIT_BATS) $(ENGINE_BATS)
2626
SRC := $(sort $(wildcard src/*.sh))
2727

28-
.PHONY: test test-unit test-engine test-acceptance test-security test-nightly structure lint setup build build-check _bats-check
28+
.PHONY: test test-unit test-engine test-acceptance test-security test-nightly structure lint lint-ci setup build build-check _bats-check
2929
setup:
3030
git submodule update --init --recursive
3131
_bats-check:
@@ -53,6 +53,10 @@ structure:
5353
lint:
5454
shellcheck -S warning bin/sluice
5555

56+
# advisory actionlint over .github/workflows - the same digest-pinned image as the scans.yml lane.
57+
lint-ci:
58+
docker run --rm --platform linux/amd64 -v "$(CURDIR):/repo" --workdir /repo rhysd/actionlint:1.7.12@sha256:9d36088643581e728c969f35141f88139fec77280b2be23c1f66f8e40e1025e7 -color
59+
5660
# bin/sluice is a GENERATED single-file launcher (assembled from the ordered src/*.sh slices, so the
5761
# curl-one-file install still works). Edit the slices, then `make build`.
5862
build:

test/verify-ci-supplychain-unit.bats

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,31 @@ EOF
4646
run grep -F "if: matrix.suite == 'lock'" "$wf"
4747
assert_success
4848
}
49+
50+
@test "supplychain: every scans.yml job is advisory (continue-on-error, never a required gate)" {
51+
local wf="$ROOT/.github/workflows/scans.yml"
52+
[ "$(grep -c 'runs-on:' "$wf")" = 3 ]
53+
[ "$(grep -c 'continue-on-error: true' "$wf")" = 3 ]
54+
}
55+
56+
@test "supplychain: scans.yml stays read-only (no SARIF/security-events, no write grants)" {
57+
local wf="$ROOT/.github/workflows/scans.yml"
58+
run grep -F 'security-events' "$wf"
59+
assert_failure
60+
run grep -F ': write' "$wf"
61+
assert_failure
62+
run grep -F 'contents: read' "$wf"
63+
assert_success
64+
}
65+
66+
@test "supplychain: every workflow action ref is SHA-pinned (fleet-wide)" {
67+
run bash -c "grep -h 'uses:' \"$ROOT\"/.github/workflows/*.yml | grep -Ev '@[0-9a-f]{40}'"
68+
assert_failure
69+
}
70+
71+
@test "supplychain: make lint-ci runs the same digest-pinned actionlint image as scans.yml" {
72+
local mk wf
73+
mk="$(grep -o 'rhysd/actionlint:[^ ]*' "$ROOT/Makefile" | head -1)"
74+
wf="$(grep -o 'rhysd/actionlint:[^ ]*' "$ROOT/.github/workflows/scans.yml" | head -1)"
75+
[ -n "$mk" ] && [ "$mk" = "$wf" ]
76+
}

0 commit comments

Comments
 (0)