Skip to content

Commit 5aa5e68

Browse files
authored
ci: harden GitHub Actions workflows (#396)
* Pin GitHub Actions to SHA hashes * Scope workflow permissions to per-job least privilege Set permissions: {} at workflow level in publish-image.yml and move permissions down to each individual job: build gets contents/packages, manifest gets contents/packages/id-token for cosign keyless signing. * Harden workflows: add persist-credentials and per-job permissions - Add persist-credentials: false to all checkout steps (artipacked) - Add permissions: {} workflow-level and contents: read per-job in ci.yml (excessive-permissions) - Add cooldown: default-days: 10 to both dependabot ecosystems (dependabot-cooldown) * Fix template-injection: pass steps.meta.outputs.tags through env vars * Add zizmor CI job to audit GitHub Actions workflows * Configure dependabot to batch github-actions updates weekly with cooldown * Use semver-granular cooldowns for bundler dependabot ecosystem Replaces the generic default-days cooldown with semver-granular values so low-risk patches flow faster while major bumps get more soak time. Also corrects github-actions cooldown from 10 days to 7 days.
1 parent 01791b2 commit 5aa5e68

3 files changed

Lines changed: 82 additions & 25 deletions

File tree

.github/dependabot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@ updates:
88
groups:
99
development-dependencies:
1010
dependency-type: "development"
11+
cooldown:
12+
semver-major-days: 7
13+
semver-minor-days: 3
14+
semver-patch-days: 2
15+
default-days: 7
1116
- package-ecosystem: github-actions
1217
directory: "/"
18+
groups:
19+
github-actions:
20+
patterns:
21+
- "*"
1322
schedule:
1423
interval: weekly
1524
open-pull-requests-limit: 10
25+
cooldown:
26+
default-days: 7

.github/workflows/ci.yml

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ on:
55
push:
66
branches: [ main ]
77

8+
permissions: {}
9+
810
jobs:
911
scan_ruby:
1012
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
1115

1216
steps:
1317
- name: Checkout code
14-
uses: actions/checkout@v4
18+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
19+
with:
20+
persist-credentials: false
1521

1622
- name: Set up Ruby
17-
uses: ruby/setup-ruby@v1
23+
uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
1824
with:
1925
ruby-version: .ruby-version
2026
bundler-cache: true
@@ -24,13 +30,17 @@ jobs:
2430

2531
scan_js:
2632
runs-on: ubuntu-latest
33+
permissions:
34+
contents: read
2735

2836
steps:
2937
- name: Checkout code
30-
uses: actions/checkout@v4
38+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
39+
with:
40+
persist-credentials: false
3141

3242
- name: Set up Ruby
33-
uses: ruby/setup-ruby@v1
43+
uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
3444
with:
3545
ruby-version: .ruby-version
3646
bundler-cache: true
@@ -40,21 +50,46 @@ jobs:
4050

4151
lint:
4252
runs-on: ubuntu-latest
53+
permissions:
54+
contents: read
4355
steps:
4456
- name: Checkout code
45-
uses: actions/checkout@v4
57+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
58+
with:
59+
persist-credentials: false
4660

4761
- name: Set up Ruby
48-
uses: ruby/setup-ruby@v1
62+
uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
4963
with:
5064
ruby-version: .ruby-version
5165
bundler-cache: true
5266

5367
- name: Lint code for consistent style
5468
run: bin/rubocop -f github
5569

70+
lint-actions:
71+
name: GitHub Actions audit
72+
runs-on: ubuntu-latest
73+
permissions:
74+
contents: read
75+
76+
steps:
77+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
78+
with:
79+
persist-credentials: false
80+
81+
- name: Run actionlint
82+
uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11
83+
84+
- name: Run zizmor
85+
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
86+
with:
87+
advanced-security: false
88+
5689
test:
5790
runs-on: ubuntu-latest
91+
permissions:
92+
contents: read
5893

5994
# services:
6095
# redis:
@@ -67,10 +102,12 @@ jobs:
67102
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libsqlite3-0 libvips
68103

69104
- name: Checkout code
70-
uses: actions/checkout@v4
105+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
106+
with:
107+
persist-credentials: false
71108

72109
- name: Set up Ruby
73-
uses: ruby/setup-ruby@v1
110+
uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
74111
with:
75112
ruby-version: .ruby-version
76113
bundler-cache: true
@@ -82,7 +119,7 @@ jobs:
82119
run: bin/rails db:setup test test:system
83120

84121
- name: Keep screenshots from failed system tests
85-
uses: actions/upload-artifact@v4
122+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
86123
if: failure()
87124
with:
88125
name: screenshots

.github/workflows/publish-image.yml

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ concurrency:
1313
group: publish-${{ github.workflow }}-${{ github.ref }}
1414
cancel-in-progress: true
1515

16-
permissions:
17-
contents: read
18-
packages: write
19-
id-token: write
20-
attestations: write
16+
permissions: {}
2117

2218
env:
2319
IMAGE_DESCRIPTION: Instantly publish your own books on the web for free, no publisher required.
@@ -28,6 +24,9 @@ jobs:
2824
name: Build and push image (${{ matrix.arch }})
2925
runs-on: ${{ matrix.runner }}
3026
timeout-minutes: 45
27+
permissions:
28+
contents: read
29+
packages: write
3130
strategy:
3231
fail-fast: false
3332
matrix:
@@ -43,13 +42,15 @@ jobs:
4342
IMAGE_NAME: ${{ github.repository }}
4443
steps:
4544
- name: Checkout
46-
uses: actions/checkout@v5.0.0
45+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
46+
with:
47+
persist-credentials: false
4748

4849
- name: Set up Docker Buildx
49-
uses: docker/setup-buildx-action@v3.11.1
50+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
5051

5152
- name: Log in to GHCR
52-
uses: docker/login-action@v3.5.0
53+
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
5354
with:
5455
registry: ${{ env.REGISTRY }}
5556
username: ${{ github.actor }}
@@ -66,7 +67,7 @@ jobs:
6667
6768
- name: Extract Docker metadata (tags, labels) with arch suffix
6869
id: meta
69-
uses: docker/metadata-action@v5.8.0
70+
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
7071
with:
7172
images: ${{ steps.vars.outputs.canonical }}
7273
tags: |
@@ -84,7 +85,7 @@ jobs:
8485
8586
- name: Build and push (${{ matrix.platform }})
8687
id: build
87-
uses: docker/build-push-action@v6.18.0
88+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
8889
with:
8990
context: .
9091
file: Dockerfile
@@ -114,15 +115,19 @@ jobs:
114115
if: github.event_name != 'pull_request'
115116
runs-on: ubuntu-latest
116117
timeout-minutes: 20
118+
permissions:
119+
contents: read
120+
packages: write
121+
id-token: write
117122
env:
118123
REGISTRY: ghcr.io
119124
IMAGE_NAME: ${{ github.repository }}
120125
steps:
121126
- name: Set up Docker Buildx (for imagetools)
122-
uses: docker/setup-buildx-action@v3.11.1
127+
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
123128

124129
- name: Log in to GHCR
125-
uses: docker/login-action@v3.5.0
130+
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
126131
with:
127132
registry: ${{ env.REGISTRY }}
128133
username: ${{ github.actor }}
@@ -139,7 +144,7 @@ jobs:
139144
140145
- name: Compute base tags (no suffix)
141146
id: meta
142-
uses: docker/metadata-action@v5.8.0
147+
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
143148
with:
144149
images: ${{ steps.vars.outputs.canonical }}
145150
tags: |
@@ -159,7 +164,7 @@ jobs:
159164
shell: bash
160165
run: |
161166
set -eu
162-
tags="${{ steps.meta.outputs.tags }}"
167+
tags="${STEPS_META_OUTPUTS_TAGS}"
163168
echo "Creating manifests for tags:"
164169
printf '%s\n' "$tags"
165170
while IFS= read -r tag; do
@@ -183,18 +188,22 @@ jobs:
183188
"${src_tag}-arm64"
184189
fi
185190
done <<< "$tags"
191+
env:
192+
STEPS_META_OUTPUTS_TAGS: ${{ steps.meta.outputs.tags }}
186193

187194
- name: Install Cosign
188-
uses: sigstore/cosign-installer@v3.9.2
195+
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
189196

190197
- name: Cosign sign all tags (keyless OIDC)
191198
shell: bash
192199
run: |
193200
set -eu
194-
tags="${{ steps.meta.outputs.tags }}"
201+
tags="${STEPS_META_OUTPUTS_TAGS}"
195202
printf '%s\n' "$tags"
196203
while IFS= read -r tag; do
197204
[ -z "$tag" ] && continue
198205
echo "Signing $tag"
199206
cosign sign --yes "$tag"
200207
done <<< "$tags"
208+
env:
209+
STEPS_META_OUTPUTS_TAGS: ${{ steps.meta.outputs.tags }}

0 commit comments

Comments
 (0)