feat(zizmor): keep the policy in one place instead of in every consumer - #339
Conversation
Of the 16 lines in .github/zizmor.yml, two are policy and the rest explains it. Because every consumer carried a copy and the drift check compared bytes, rewording that explanation in #336 meant a pull request in 54 repositories for no change in what zizmor reports. The reusable now fetches the organisation policy from netresearch/.github at run time when the audited repo has no .github/zizmor.yml of its own, and the file leaves all five templates. A repo that needs a different policy still keeps its own file: it is present, so nothing is fetched and the local one wins. Consumers that still carry a copy are unaffected for the same reason. The fetch is a sparse checkout of the single file, and a missing policy is a hard failure rather than a silent fall-through to zizmor's defaults, which would flag every first-party ref-pinned reusable. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR centralizes zizmor policy by removing per-template .github/zizmor.yml copies and teaching the reusable zizmor workflow to fetch the organization policy from a configurable repo/ref at runtime when a consumer repo doesn’t define its own config.
Changes:
- Removed
.github/zizmor.ymlfrom five project templates to avoid duplicated policy/docs. - Added
config-repo/config-refinputs and a sparse-checkout fetch+install flow in the zizmor reusable workflow. - Explicitly fails the job if the org policy cannot be read, preventing audits from running with zizmor defaults.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| templates/typo3-extension/.github/zizmor.yml | Removed duplicated zizmor policy/config from template. |
| templates/skill/.github/zizmor.yml | Removed duplicated zizmor policy/config from template. |
| templates/php-module/.github/zizmor.yml | Removed duplicated zizmor policy/config from template. |
| templates/go-lib/.github/zizmor.yml | Removed duplicated zizmor policy/config from template. |
| templates/go-app/.github/zizmor.yml | Removed duplicated zizmor policy/config from template. |
| .github/workflows/zizmor.yml | Fetches and installs org zizmor policy when local config is absent; adds inputs and failure guardrails. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review found three things on the fetch step. The configurable config-repo and config-ref made the checkout target caller-controlled, which is the untrusted-checkout flow CodeQL flags (actions/untrusted-checkout/medium, alert 185); nothing had asked for that flexibility, so the source is now fixed at netresearch/.github@main. That also puts the ref into the diagnostics, which were naming only the repository. The copy now rejects a symlink rather than letting cp dereference whatever it points at. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/workflows/zizmor.yml:74
- The org-policy checkout uses a fixed path
.zizmor-organd laterrm -rf .zizmor-org. In a consumer repo, a tracked.zizmor-orgfile/dir (or a symlink) would either be overwritten byactions/checkoutor deleted by the cleanup, and a symlink could also redirect the checkout target. Add a guard before the checkout to fail fast if.zizmor-orgalready exists in the audited repo, so the reusable never clobbers or deletes repo content.
- name: Fetch the organisation zizmor config
id: org-config
if: hashFiles('.github/zizmor.yml') == ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
netresearch/.github#339 makes the zizmor reusable fetch the organisation policy at run time when a repo has no .github/zizmor.yml of its own, and removes the file from the templates. Keeping a copy here would silently win over the shared policy the next time it really changes, which is how this file came to differ across the fleet in the first place. The scan itself is unchanged: the same policy is applied, from one place. Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.
) Removes `.github/zizmor.yml`. [netresearch/.github#339](netresearch/.github#339) makes the zizmor reusable fetch the organisation policy at run time when a repo has no file of its own, and drops it from all five templates, so this copy no longer has a job. It is not harmless to leave behind. A local file takes precedence over the fetched one, so this repository would keep running an old policy the next time the shared one actually changes — which is exactly how a two-line policy ended up needing a pull request in 54 repositories to reword a comment. The scan result does not change: the same policy is applied, from one place instead of 54. A repository that genuinely needs a different policy opts out by keeping its own `.github/zizmor.yml`; this one was byte-identical to the template, which was verified before deleting. This pull request previously carried the comment rewording from [#336](netresearch/.github#336). That became pointless once the file itself moved, so the branch was rewritten to delete it instead.



Of the 16 lines in
.github/zizmor.yml, two are policy —"netresearch/*": ref-pinand"*": hash-pin— and the other fourteen explain them. Every consumer carried a copy, and the drift check compared bytes, so rewording that explanation in #336 required a pull request in 54 repositories without changing a single thing zizmor reports.What changes. The reusable fetches the organisation policy from this repository at run time when the audited repo has no
.github/zizmor.yml, and the file leaves all five templates. The fetch is a sparse checkout of that one path, verified to land exactly.zizmor-org/.github/zizmor.ymland nothing else.Backwards compatible. A repo that still carries its own copy — which today is all 54 of them — has a non-empty
hashFilesresult for that path, skips the fetch and uses its local file, exactly as before. The same mechanism is how a repo opts out: keep a.github/zizmor.ymlwith a different policy and it wins.No silent fall-through. If the policy cannot be read from
config-repo, the job fails rather than auditing with zizmor's defaults, which would flag every first-party ref-pinned reusable and bury the real findings.config-repoandconfig-refare inputs so a fork or a test run can point elsewhere; both default tonetresearch/.github@main.Follow-up once this lands: the copy in each consumer becomes dead weight that would go stale the next time the policy really changes, so it should be deleted there — the last sync wave this file will ever need.
actionlintis clean.