Skip to content

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.

WeekAction
Week 0Announce in internal channels. Publish this runbook. Share the tooling docs.
Week 1Create 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 2Enable 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 3Open 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 4When 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 5Flip the ruleset to Active for both audit workflows.
OngoingRoll out Dependabot (Layer 4). Monitor weekly.

After full rollout, smoke-test by opening a PR with this workflow in any repo:

name: smoke-test
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # ← unpinned, should fail

Expected outcomes:

  1. Workflow startup fails with the SHA-pinning error → Layer 1 working.
  2. The zizmor check fails on the PR before the workflow even tries → Layer 2 working.
  3. A destructive command in a run: block trips the scan → Layer 2b working.
  4. The merge button is greyed out with a “Required workflow / status check” message → Layer 3 working.
  5. Weekly Dependabot PRs appear in repos → Layer 4 working.
  • 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=all may 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_group in their triggers, otherwise queued PRs won’t trigger them.

List repos and their default branches in an org:

Terminal window
gh api graphql -f query='
query($org: String!) {
organization(login: $org) {
repositories(first: 100) {
nodes { name defaultBranchRef { name } }
}
}
}' -f org=your-org

Check ruleset status:

Terminal window
gh api /orgs/your-org/rulesets --jq '.[].name'

Run zizmor locally before pushing:

Terminal window
brew install zizmor # or: cargo install zizmor
zizmor .github/workflows/
zizmor --fix=all .github/workflows/