Skip to content

Commit 14b4e4a

Browse files
committed
ci: harden CI per Astral's open-source security recommendations
Adopts the supply-chain practices described in Astral's "Open source security at Astral" post (https://astral.sh/blog/open-source-security-at-astral) and applies them to the FrankenPHP CI: - Add a zizmor workflow that audits every PR/push touching .github (and weekly on a schedule) as a hard gate. zizmor is installed via pipx so the security workflow itself has minimal supply chain. - Tighten every workflow to start with `permissions: {}` and grant the minimum permissions per job, so newly added jobs inherit nothing. - Track Docker base images with Dependabot so security patches in the underlying images surface as reviewable PRs. - Document the hardened posture (zizmor gate, least-privilege perms, environment-scoped secrets, build provenance, no pull_request_target) in SECURITY.md. - Document hash-pinning as the next ratchet in zizmor.yaml; the policy stays at `ref-pin` for now to avoid breaking existing tag pins, with a clear path to switch to `hash-pin` once Dependabot starts updating by SHA. https://claude.ai/code/session_01YEC8c9bU4Mory8FFoiAS5T
1 parent 239ad52 commit 14b4e4a

12 files changed

Lines changed: 152 additions & 19 deletions

.github/dependabot.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,21 @@ updates:
3737
- "*"
3838
cooldown:
3939
default-days: 7
40+
# Docker base images. Astral recommends keeping the supply chain
41+
# under continuous review; Dependabot opens a PR whenever a new base
42+
# image becomes available so we never silently fall behind security
43+
# patches.
44+
- package-ecosystem: docker
45+
directories:
46+
- /
47+
- /caddy
48+
schedule:
49+
interval: weekly
50+
commit-message:
51+
prefix: chore(docker)
52+
groups:
53+
docker:
54+
patterns:
55+
- "*"
56+
cooldown:
57+
default-days: 7

.github/workflows/docker.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ on:
3030
type: string
3131
schedule:
3232
- cron: "0 4 * * *"
33-
permissions:
34-
contents: read
33+
permissions: {}
3534
env:
3635
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
3736
jobs:
3837
prepare:
3938
runs-on: ubuntu-24.04
39+
permissions:
40+
contents: read
4041
outputs:
4142
# Push if it's a scheduled job, a tag, or if we're committing to the main branch
4243
push: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false }}
@@ -82,6 +83,8 @@ jobs:
8283
build:
8384
environment: dockerhub
8485
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
86+
permissions:
87+
contents: read
8588
needs:
8689
- prepare
8790
if: ${{ !fromJson(needs.prepare.outputs.skip) }}
@@ -207,6 +210,8 @@ jobs:
207210
push:
208211
environment: dockerhub
209212
runs-on: ubuntu-24.04
213+
permissions:
214+
contents: read
210215
needs:
211216
- prepare
212217
- build

.github/workflows/lint.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ on:
1010
push:
1111
branches:
1212
- main
13-
permissions:
14-
contents: read
15-
packages: read
16-
statuses: write
13+
permissions: {}
1714
jobs:
1815
build:
1916
name: Lint Code Base
2017
runs-on: ubuntu-latest
18+
permissions:
19+
contents: read
20+
packages: read
21+
statuses: write
2122
steps:
2223
- name: Checkout Code
2324
uses: actions/checkout@v6

.github/workflows/sanitizers.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ on:
1414
- main
1515
paths-ignore:
1616
- "docs/**"
17-
permissions:
18-
contents: read
17+
permissions: {}
1918
env:
2019
GOTOOLCHAIN: local
2120
GOTESTSUM_FORMAT: pkgname-and-test-fails
@@ -24,6 +23,8 @@ jobs:
2423
sanitizers:
2524
name: ${{ matrix.sanitizer }}
2625
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
2728
strategy:
2829
fail-fast: false
2930
matrix:

.github/workflows/static.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ on:
3232
schedule:
3333
- cron: "0 0 * * *"
3434

35-
permissions:
36-
contents: read
35+
permissions: {}
3736

3837
env:
3938
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
@@ -43,6 +42,8 @@ env:
4342
jobs:
4443
prepare:
4544
runs-on: ubuntu-24.04
45+
permissions:
46+
contents: read
4647
outputs:
4748
push: ${{ toJson((steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false) }}
4849
platforms: ${{ steps.matrix.outputs.platforms }}
@@ -381,6 +382,8 @@ jobs:
381382
push:
382383
environment: dockerhub
383384
runs-on: ubuntu-24.04
385+
permissions:
386+
contents: read
384387
needs:
385388
- prepare
386389
- build-linux-musl

.github/workflows/tests.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ on:
1414
- main
1515
paths-ignore:
1616
- "docs/**"
17-
permissions:
18-
contents: read
17+
permissions: {}
1918
env:
2019
GOTOOLCHAIN: local
2120
GOEXPERIMENT: cgocheck2
@@ -25,6 +24,8 @@ jobs:
2524
name: Tests (Linux, PHP ${{ matrix.php-versions }})
2625
runs-on: ubuntu-latest
2726
continue-on-error: false
27+
permissions:
28+
contents: read
2829
strategy:
2930
fail-fast: false
3031
matrix:
@@ -98,6 +99,8 @@ jobs:
9899
integration-tests:
99100
name: Integration Tests (Linux, PHP ${{ matrix.php-versions }})
100101
runs-on: ubuntu-latest
102+
permissions:
103+
contents: read
101104
strategy:
102105
fail-fast: false
103106
matrix:
@@ -145,6 +148,8 @@ jobs:
145148
tests-mac:
146149
name: Tests (macOS, PHP 8.5)
147150
runs-on: macos-latest
151+
permissions:
152+
contents: read
148153
env:
149154
HOMEBREW_NO_AUTO_UPDATE: 1
150155
GOFLAGS: "-tags=nowatcher,nobadger,nomysql,nopgx"

.github/workflows/translate.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ on:
88
- main
99
paths:
1010
- "docs/*"
11-
permissions:
12-
contents: write
13-
pull-requests: write
11+
permissions: {}
1412
jobs:
1513
build:
1614
environment: translate
1715
name: Translate Docs
1816
runs-on: ubuntu-latest
17+
permissions:
18+
contents: write
19+
pull-requests: write
1920
steps:
2021
- name: Checkout Code
2122
uses: actions/checkout@v6

.github/workflows/windows.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ on:
2828
schedule:
2929
- cron: "0 8 * * *"
3030

31-
permissions:
32-
contents: read
31+
permissions: {}
3332

3433
env:
3534
GOTOOLCHAIN: local

.github/workflows/wrap-issue-details.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ on:
33
issues:
44
types: [opened, edited]
55

6-
permissions:
7-
contents: read
6+
permissions: {}
87

98
jobs:
109
wrap_content:

.github/workflows/zizmor.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
# Static analysis of GitHub Actions workflows.
3+
#
4+
# Inspired by Astral's open-source security guidance:
5+
# https://astral.sh/blog/open-source-security-at-astral
6+
#
7+
# zizmor catches misuses such as template injection, credential leaks,
8+
# excessive permissions, dangerous triggers and impostor commits. It is
9+
# a hard CI gate: a failed audit blocks the pull request.
10+
name: GitHub Actions Security Audit
11+
concurrency:
12+
cancel-in-progress: true
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
on:
15+
pull_request:
16+
branches:
17+
- main
18+
paths:
19+
- .github/**
20+
- zizmor.yaml
21+
push:
22+
branches:
23+
- main
24+
paths:
25+
- .github/**
26+
- zizmor.yaml
27+
schedule:
28+
# Re-audit weekly so newly published advisories surface even if
29+
# no workflow file has changed.
30+
- cron: "0 6 * * 1"
31+
permissions: {}
32+
jobs:
33+
zizmor:
34+
name: zizmor
35+
runs-on: ubuntu-latest
36+
permissions:
37+
contents: read
38+
# Required so zizmor can upload SARIF results to GitHub Code
39+
# Scanning. Drop this permission if Code Scanning is unavailable.
40+
security-events: write
41+
steps:
42+
- name: Checkout repository
43+
uses: actions/checkout@v6
44+
with:
45+
persist-credentials: false
46+
# zizmor is installed via pipx (preinstalled on ubuntu-latest)
47+
# rather than a third-party action so the security workflow has
48+
# the smallest possible supply chain.
49+
- name: Install zizmor
50+
run: pipx install zizmor
51+
- name: Run zizmor
52+
env:
53+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
run: zizmor --format=sarif --min-severity=low . > zizmor.sarif
55+
- name: Upload SARIF file
56+
if: ${{ !cancelled() }}
57+
uses: github/codeql-action/upload-sarif@v4
58+
with:
59+
sarif_file: zizmor.sarif
60+
category: zizmor

0 commit comments

Comments
 (0)