ci: harden CI per Astral's open-source security recommendations #2347
Conversation
There was a problem hiding this comment.
Pull request overview
Hardens the repository’s GitHub Actions CI posture by enforcing least-privilege workflow permissions and pinning third-party actions, aligning with Astral’s supply-chain security recommendations.
Changes:
- Set workflow-level
permissions: {}across workflows and add minimal per-job permissions where needed. - Pin GitHub Actions
uses:references to immutable commit SHAs. - Add a Dependabot auto-merge workflow for non-major GitHub Actions updates and update zizmor policy configuration.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
zizmor.yaml |
Tightens unpinned-uses policy (now configured to enforce stronger pinning). |
.github/workflows/wrap-issue-details.yaml |
Drops default permissions and pins actions/github-script by SHA. |
.github/workflows/windows.yaml |
Drops default permissions and SHA-pins key actions in Windows build workflow. |
.github/workflows/translate.yaml |
Moves permissions to job scope and SHA-pins actions used for translation + PR creation. |
.github/workflows/tests.yaml |
Adds job-scoped contents: read and SHA-pins actions used in test jobs. |
.github/workflows/static.yaml |
Adds job-scoped permissions and SHA-pins Docker/Actions dependencies (incl. provenance attestation action). |
.github/workflows/sanitizers.yaml |
Adds job-scoped contents: read and SHA-pins checkout/go/cache actions. |
.github/workflows/lint.yaml |
Moves permissions to job scope and SHA-pins checkout + super-linter. |
.github/workflows/docker.yaml |
Adds job-scoped permissions and SHA-pins Docker/Actions dependencies. |
.github/workflows/dependabot.yaml |
Introduces a Dependabot PR auto-merge workflow and SHA-pins metadata action. |
.github/actions/watcher/action.yaml |
SHA-pins actions/cache used by the composite action. |
Comments suppressed due to low confidence (1)
zizmor.yaml:6
- The PR description says the zizmor policy will stay at
ref-pinfor now, but this change switches it tohash-pin. Either update the PR description/SECURITY docs to match, or keep the policy atref-pinuntil the repo is ready to enforce hash pinning everywhere.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,25 @@ | |||
| --- | |||
| name: Dependabot Auto-Merge | |||
There was a problem hiding this comment.
Hear me out. This logic seems kinda circular? Lock down all the things to prevent supply-chain attacks ... but then auto-merge updates, which is the most likely vector for supply-chain attacks?
Don't get me wrong, I completely agree with this workflow (automating updates FTW). Just pointing out the logic.
There was a problem hiding this comment.
I'm against dependabot auto merges. Most of the time it's broken and even in case it does manage to get a working PR, it's likely that it missed other packages, so we need to manually touch it up anyway. Auto merges in general are also a bit tricky in my eyes, many vulnerabilities and projects takeovers have been achieved by auto CI runs or merges.
There was a problem hiding this comment.
I'm also not a fan of SHAs for these same reasons. Dependabot updates the SHA and the comment ... but I still got to go check the SHA is actually what the comment says, which is a branch head. At that point, might as well lock it to the branch instead of the SHA.
There was a problem hiding this comment.
With the cooldown period, this gives some time for the supply chain attacks to be detected (not the case currently), while not increasing maintenance burden for us (currently, new minor versions of our GitHub Actions dependencies are automatically used).
There was a problem hiding this comment.
Pinning to SHA also provides better traceability and reproducibility: we can easily know what exact versions of all our dependencies have been used to build a release.
|
WDYT about this one @php/frankenphp-collaborators? #2347 (comment) |
|
I have mixed feelings about it. The cooldown only works as long as someone else is willing to run the branch HEAD and be the canary. If everyone adopts the same cooldown ... it stops serving any defensive purpose; we're just waiting an extra week for real bugfixes. We're effectively free-riding on people being less cautious than us, and the more widely the pattern spreads, the less it actually buys anyone. On SHA pinning: reproducibility feels like a logging concern rather than an implementation one. We can resolve the SHA at build time -- its in the logs -- without giving up the "we're running v4" at-a-glance. And pinning creates its own maintenance pain ... if a commit lands in both v4 and v5 (cherry-picks happen), which version does the pin represent? We're back to trusting the comment, which defeats the point.
Extending @henderkes's reliability concern: imagine dependabot updates us to a SHA that turns out to have a known issue. We downgrade. On dependabot's next run, it'll just re-upgrade us to the same broken SHA, and it won't pick up the hotfix commit because the hotfix hasn't cooled down yet. So we're exposed to the bug through the first cooldown window, then locked out of the fix through the second ... cooldown can introduce a failure mode rather than mitigating one. I acknowledge these are edge cases, but these are new edge cases that currently don't exist. This is mostly where my concerns lie. If we acknowledge these new cases and have a way to deal with them; then 🚢 it. |
|
I think I like the SHA pinning, I can see it preventing certain supply chain attacks, even if there's some cost to maintainability. I dislike the auto-merges though since (as already mentioned) updates might also silently break stuff. Maybe that almost never happens, we can also trial it. |
|
Do you all agree to trialing it? We'll roll it back if it proves to be annoying. Regarding your concerns @withinboredom, I'm certain many projects like to live on the edge, but since FrankenPHP is becoming a critical piece of infrastructure, I don't think it's the right place to test the latest versions of GitHub Actions. Also, most actions we use are maintained by major companies (Microsoft, Docker, etc.) that likely "dogfood" their own products first and don't need us to beta-test for them. |
|
I'm still not a fan of the auto updates and merges for the reasons outlined earlier, but I agree with pinning to commit hashes.
Sorry, but I had to grin reading that. I'll bring up my primary concern again:
This is something we shouldn't roll out, unless we're 100% sure it cannot have negative consequences. If you are, I don't object to merging it, though I would prefer to remove the auto merge part because it will likely not have anything useful come of it anyway. |
|
Automatic merging is quite similar to what we have now by referencing major version only isn't it? |
|
Oh, the automatic merge is limited to gha minor and patch updates. That still kind of defeats zizmor's point, but doesn't trigger what worried me. I understood that all dependabot updates would always be auto merged. |
|
This branch is behind #2369, which dropped |
899cfb6 to
831162e
Compare
|
Are you ok to merge the last version of this one @php/frankenphp-admin? |
Pin every external action (workflows and composite actions) to a full commit SHA with the exact version in the comment Dependabot keeps current, then drop the zizmor unpinned-uses ref-pin override so the default hash-pin policy enforces it. Mitigates the tag-mutation supply-chain class. The secrets-outside-env DOCKERHUB_TOKEN allowlist stays.
Enable auto-merge only for github_actions minor/patch bumps; majors and other ecosystems still need a human. The default GITHUB_TOKEN lacks the workflows scope and is refused on PRs that touch .github/workflows, so use the dunglas-release app token scoped to contents/pull-requests/workflows, run from an unprotected dependabot environment. zizmor ignores the required pull_request_target trigger for this guarded workflow.
831162e to
f9ae739
Compare
| rules: | ||
| unpinned-uses: | ||
| config: | ||
| policies: | ||
| "*": ref-pin | ||
| # Dependabot auto-merge requires pull_request_target to obtain repo-write | ||
| # permissions; the job is guarded by an `if:` check on the PR author. | ||
| dangerous-triggers: | ||
| ignore: |
| GOFLAGS: "-tags=nobadger,nomysql,nopgx" | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |

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:
(and weekly on a schedule) as a hard gate. zizmor is installed via
pipx so the security workflow itself has minimal supply chain.
permissions: {}and grant theminimum permissions per job, so newly added jobs inherit nothing.
underlying images surface as reviewable PRs.
environment-scoped secrets, build provenance, no pull_request_target)
in SECURITY.md.
stays at
ref-pinfor now to avoid breaking existing tag pins, witha clear path to switch to
hash-pinonce Dependabot starts updatingby SHA.