Rollout, Verification & Pitfalls
A staged rollout avoids breaking every repo on day one. Sequencing matters: Layer 1 has no audit mode and breaks unpinned workflows the instant it’s enabled, so it must come after static analysis has surfaced and fixed the bulk of unpinned references.
Staged rollout plan
Section titled “Staged rollout plan”| Week | Action |
|---|---|
| Week 0 | Announce in internal channels. Publish this runbook. Share the tooling docs. |
| Week 1 | Create the security-workflows repo (Layer 2). Land the audit workflow. Open auto-fix PRs in 3–5 pilot repos. Pilot command scanning in the same repos. |
| Week 2 | Enable the ruleset in Evaluate mode. Run static analysis across all repos to inventory unpinned-uses findings — this is your dry-run for Layer 1. |
| Week 3 | Open auto-fix PRs across all repos. Triage and merge. Track remediation rate. Keep --fail-on error for command scanning; don’t enable warning-level blocking yet. |
| Week 4 | When unpinned-uses findings approach zero and high-severity command findings are resolved, enable SHA pinning (Layer 1). Have a fast-track allowlist process and an on-call rotation ready for the first 48 hours. |
| Week 5 | Flip the ruleset to Active for both audit workflows. |
| Ongoing | Roll out Dependabot (Layer 4). Monitor weekly. |
End-to-end verification
Section titled “End-to-end verification”After full rollout, smoke-test by opening a PR with this workflow in any repo:
name: smoke-teston: pull_requestjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # ← unpinned, should failExpected outcomes:
- Workflow startup fails with the SHA-pinning error → Layer 1 working.
- The zizmor check fails on the PR before the workflow even tries → Layer 2 working.
- A destructive command in a
run:block trips the scan → Layer 2b working. - The merge button is greyed out with a “Required workflow / status check” message → Layer 3 working.
- Weekly Dependabot PRs appear in repos → Layer 4 working.
Common pitfalls
Section titled “Common pitfalls”- Sub-action breakage. Third-party actions that internally use tag-pinned sub-actions break under Layer 1. Temporarily allowlist by SHA while you file an upstream issue.
- Windows shell quoting.
zizmor --fix=allmay produce PowerShell-incompatible env var quoting. Review diffs before merging on Windows-runner repos. - Fork and contributor PRs. Required workflows still run on fork PRs, but secrets are unavailable — make sure the audit path doesn’t depend on secrets.
- Local actions.
./...references are exempt from SHA-pinning enforcement but are still audited by static analysis. - Reusable workflows. Not covered by SHA-pinning enforcement by design. Add a custom static-analysis rule if you want them pinned too.
- Merge queues. If you use them, ensure the audit workflows include
merge_groupin their triggers, otherwise queued PRs won’t trigger them.
Useful CLI snippets
Section titled “Useful CLI snippets”List repos and their default branches in an org:
gh api graphql -f query=' query($org: String!) { organization(login: $org) { repositories(first: 100) { nodes { name defaultBranchRef { name } } } } }' -f org=your-orgCheck ruleset status:
gh api /orgs/your-org/rulesets --jq '.[].name'Run zizmor locally before pushing:
brew install zizmor # or: cargo install zizmorzizmor .github/workflows/zizmor --fix=all .github/workflows/