Skip to content

Commit a04a388

Browse files
flavorjonesclaude
andauthored
ci: harden GitHub Actions workflows (#109)
* ci: add zizmor and actionlint CI job to test workflow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: increase dependabot cooldown for github-actions to 7 days Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: add lint-actions target and tool installation to Makefile Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: pin all actions to SHA hashes with version comments Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: resolve all high-severity zizmor findings - Suppress dangerous-triggers in ai-labeler and sensitive-change-gate (both only call reusable workflows, no PR code checked out) - Fix bot-conditions with dual check in dependabot-auto-merge - Suppress cache-poisoning in release (branch-isolated default cache) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: resolve all low-severity zizmor findings - Add persist-credentials: false to all checkout steps (artipacked) - Increase gomod dependabot cooldown default-days to 7 (dependabot-cooldown) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: move workflow-level permissions to job-level only Replace overly broad workflow-level permissions with permissions: {} in ai-labeler, direct-push-alert, and sensitive-change-gate. Each job already declares its own scoped permissions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: ensure all permissions are scoped to job-level Move workflow-level permissions to permissions: {} in scorecard and test workflows, adding explicit job-level permissions for each job. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d43a751 commit a04a388

10 files changed

Lines changed: 99 additions & 45 deletions

.github/dependabot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ updates:
1414
patterns:
1515
- "*"
1616
cooldown:
17-
default-days: 2
17+
default-days: 7
1818
semver-major-days: 7
1919
semver-minor-days: 3
2020
semver-patch-days: 2
@@ -35,6 +35,6 @@ updates:
3535
patterns:
3636
- "*"
3737
cooldown:
38-
default-days: 2
38+
default-days: 7
3939
commit-message:
4040
prefix: "ci"

.github/workflows/ai-labeler.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
name: Classify PR
22

33
on:
4-
pull_request_target:
4+
pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only calls reusable workflows, no PR code is checked out or executed
55
types: [opened, synchronize, reopened]
66

77
concurrency:
88
group: classify-pr-${{ github.event.pull_request.number }}
99
cancel-in-progress: true
1010

11-
permissions:
12-
contents: read
13-
issues: write
14-
models: read
15-
pull-requests: write
11+
permissions: {}
1612

1713
jobs:
1814
classify:

.github/workflows/dependabot-auto-merge.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ jobs:
1010
permissions:
1111
contents: write
1212
pull-requests: write
13-
if: github.actor == 'dependabot[bot]'
13+
if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' # zizmor: ignore[bot-conditions] -- dual check: actor validates current trigger, user.login validates PR origin
1414
steps:
1515
- name: Fetch Dependabot metadata
1616
id: metadata
17-
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
17+
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0
1818
with:
1919
github-token: ${{ secrets.GITHUB_TOKEN }}
2020

.github/workflows/direct-push-alert.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ on:
44
push:
55
branches: [master]
66

7-
permissions:
8-
contents: read
9-
issues: write
7+
permissions: {}
108

119
jobs:
1210
alert:

.github/workflows/release.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ jobs:
3535
attestations: write
3636
pull-requests: read
3737
steps:
38-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
38+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3939
with:
4040
fetch-depth: 0
41+
persist-credentials: false
4142

4243
- name: Set up Go
43-
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
44+
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 # zizmor: ignore[cache-poisoning] -- cache is branch-isolated; fork PRs cannot write to this cache
4445
with:
4546
go-version-file: go.mod
4647

@@ -78,7 +79,7 @@ jobs:
7879
7980
- name: Generate GitHub App token
8081
id: app-token
81-
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
82+
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
8283
with:
8384
app-id: ${{ vars.RELEASE_CLIENT_ID }}
8485
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
@@ -89,7 +90,7 @@ jobs:
8990
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
9091

9192
- name: Install Syft
92-
uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0
93+
uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1
9394

9495
- name: Generate shell completions
9596
run: |
@@ -101,7 +102,7 @@ jobs:
101102
rm fizzy-tmp
102103
103104
- name: Run GoReleaser
104-
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
105+
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
105106
with:
106107
version: 'v2.14.1'
107108
args: release --clean
@@ -127,7 +128,9 @@ jobs:
127128
contents: read
128129
if: startsWith(github.ref, 'refs/tags/v')
129130
steps:
130-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
131+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
132+
with:
133+
persist-credentials: false
131134

132135
- name: Check AUR secret
133136
id: check

.github/workflows/scorecard.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ on:
77
- cron: '30 1 * * 6'
88
workflow_dispatch:
99

10-
permissions: read-all
10+
permissions: {}
1111

1212
jobs:
1313
analysis:
1414
runs-on: ubuntu-latest
1515
permissions:
16+
actions: read
1617
security-events: write
1718
id-token: write
1819
contents: read
1920
steps:
20-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
21+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2122
with:
2223
persist-credentials: false
2324

@@ -33,7 +34,7 @@ jobs:
3334
path: results.sarif
3435
retention-days: 5
3536

36-
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
37+
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
3738
continue-on-error: true
3839
with:
3940
sarif_file: results.sarif

.github/workflows/security.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ jobs:
1818
permissions:
1919
contents: read
2020
steps:
21-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
21+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2222
with:
2323
fetch-depth: 0
24+
persist-credentials: false
2425
- name: Install gitleaks
2526
run: |
2627
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz | tar -xz
@@ -36,13 +37,15 @@ jobs:
3637
security-events: write
3738
if: github.event_name != 'pull_request'
3839
steps:
39-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
40+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
41+
with:
42+
persist-credentials: false
4043
- uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
4144
with:
4245
scan-type: fs
4346
format: sarif
4447
output: trivy-results.sarif
45-
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
48+
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
4649
with:
4750
sarif_file: trivy-results.sarif
4851
category: trivy
@@ -55,11 +58,13 @@ jobs:
5558
security-events: write
5659
if: github.event_name != 'pull_request'
5760
steps:
58-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
61+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
62+
with:
63+
persist-credentials: false
5964
- uses: securego/gosec@bb17e422fc34bf4c0a2e5cab9d07dc45a68c040c # v2.24.7
6065
with:
6166
args: -no-fail -exclude=G304,G401,G501 -exclude-dir=e2e -fmt sarif -out gosec-results.sarif ./...
62-
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
67+
- uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
6368
with:
6469
sarif_file: gosec-results.sarif
6570
category: gosec
@@ -71,5 +76,7 @@ jobs:
7176
contents: read
7277
if: github.event_name == 'pull_request'
7378
steps:
74-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
75-
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
79+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
80+
with:
81+
persist-credentials: false
82+
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0

.github/workflows/sensitive-change-gate.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
name: Sensitive Change Gate
22

33
on:
4-
pull_request_target:
4+
pull_request_target: # zizmor: ignore[dangerous-triggers] -- required for write access to PRs from forks; workflow only calls reusable workflows, no PR code is checked out or executed
55
types: [opened, synchronize, reopened]
66

7-
permissions:
8-
contents: read
9-
pull-requests: write
7+
permissions: {}
108

119
jobs:
1210
gate:

.github/workflows/test.yml

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ on:
77
branches: [master]
88
workflow_call:
99

10-
permissions:
11-
contents: read
10+
permissions: {}
1211

1312
jobs:
1413
test:
1514
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
1617
steps:
17-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
18+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19+
with:
20+
persist-credentials: false
1821

1922
- name: Set up Go
20-
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
23+
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
2124
with:
2225
go-version-file: go.mod
2326

@@ -38,11 +41,15 @@ jobs:
3841

3942
lint:
4043
runs-on: ubuntu-latest
44+
permissions:
45+
contents: read
4146
steps:
42-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
47+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
48+
with:
49+
persist-credentials: false
4350

4451
- name: Set up Go
45-
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
52+
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
4653
with:
4754
go-version-file: go.mod
4855

@@ -53,17 +60,21 @@ jobs:
5360
run: go vet ./...
5461

5562
- name: golangci-lint
56-
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9
63+
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
5764
with:
5865
version: v2.10
5966

6067
security:
6168
runs-on: ubuntu-latest
69+
permissions:
70+
contents: read
6271
steps:
63-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
72+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
73+
with:
74+
persist-credentials: false
6475

6576
- name: Set up Go
66-
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
77+
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
6778
with:
6879
go-version: 'stable'
6980

@@ -72,13 +83,35 @@ jobs:
7283
go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
7384
govulncheck ./...
7485
86+
lint-actions:
87+
name: GitHub Actions audit
88+
runs-on: ubuntu-latest
89+
permissions:
90+
contents: read
91+
steps:
92+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
93+
with:
94+
persist-credentials: false
95+
96+
- name: Run actionlint
97+
uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11
98+
99+
- name: Run zizmor
100+
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
101+
with:
102+
advanced-security: false
103+
75104
race-check:
76105
runs-on: ubuntu-latest
106+
permissions:
107+
contents: read
77108
steps:
78-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
109+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
110+
with:
111+
persist-credentials: false
79112

80113
- name: Set up Go
81-
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
114+
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
82115
with:
83116
go-version-file: go.mod
84117

Makefile

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: test test-unit test-e2e test-go test-file test-run build clean tidy help \
22
check-toolchain fmt fmt-check vet lint tidy-check race-test vuln secrets \
33
replace-check security check release-check release tools \
4-
surface-snapshot surface-check sync-skill
4+
surface-snapshot surface-check sync-skill lint-actions
55

66
BINARY := $(CURDIR)/bin/fizzy
77
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
@@ -37,6 +37,7 @@ help:
3737
@echo " make secrets Run gitleaks secret scan"
3838
@echo " make replace-check Guard against replace directives in go.mod"
3939
@echo ""
40+
@echo " make lint-actions Lint GitHub Actions workflows"
4041
@echo " make security lint + vuln + secrets"
4142
@echo " make check fmt-check + vet + lint + test-unit + tidy-check"
4243
@echo " make release-check check + replace-check + vuln + race-test"
@@ -166,11 +167,28 @@ release-check: check replace-check vuln
166167
release:
167168
@scripts/release.sh
168169

170+
# Lint GitHub Actions workflows
171+
lint-actions:
172+
actionlint
173+
zizmor .
174+
169175
# Install dev tools
170176
tools:
171177
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
172178
go install golang.org/x/vuln/cmd/govulncheck@latest
173179
@echo "For gitleaks, install via: brew install gitleaks (or see https://github.com/gitleaks/gitleaks)"
180+
@for tool in actionlint shellcheck zizmor; do \
181+
if ! command -v "$$tool" > /dev/null 2>&1; then \
182+
if command -v brew > /dev/null 2>&1; then \
183+
brew install "$$tool"; \
184+
elif command -v pacman > /dev/null 2>&1; then \
185+
sudo pacman -S --noconfirm "$$tool"; \
186+
else \
187+
echo "Error: install $$tool manually" >&2; \
188+
exit 1; \
189+
fi; \
190+
fi; \
191+
done
174192

175193
# Regenerate SURFACE.txt
176194
surface-snapshot:

0 commit comments

Comments
 (0)