Safeguard
Engineering

Securing GitHub Actions Reusable Workflows at Scale

Reusable workflows centralize CI logic — and centralize compromise. Pinning, secrets scoping, org policy, and the review process that keeps one bad merge from owning 400 repos.

Sofia Marchetti
Open Source Program Lead
6 min read

A reusable workflow is a single point of compromise for every repository that calls it: whoever can modify org/ci-workflows/.github/workflows/build.yml can execute code, read secrets, and mint OIDC tokens in hundreds of downstream pipelines at once. That's not a reason to avoid them — centralized CI logic is how you standardize security in the first place — but it means the shared-workflows repo needs the change control of a production service, and callers need to pin what they call. The tj-actions incident in March 2025 demonstrated the blast radius when 23,000+ repositories trusted a mutable tag; reusable workflows concentrate the same risk inside your org.

Pin the call, not just the actions inside it

Callers reference reusable workflows with a ref, and every ref type except a commit SHA is mutable:

jobs:
  build:
    # floats with the branch — a compromised workflows repo hits you instantly
    uses: yourorg/ci-workflows/.github/workflows/build.yml@main

  build-pinned:
    # immutable — this exact content, forever
    uses: yourorg/ci-workflows/.github/workflows/build.yml@8f4b7f84864484a7bf31766abe9204da3cbe65b3  # v2.3.1

The same discipline applies transitively: the reusable workflow itself must SHA-pin every action it invokes, because your callers inherit its choices invisibly. The tj-actions/changed-files compromise (CVE-2025-30066) worked precisely by rewriting existing version tags to point at malicious commits — every consumer pinned to @v45 got the payload, every consumer pinned to a SHA didn't. The tag-versus-SHA tradeoffs are worth reading in full, but the operational answer is: pin SHAs everywhere, keep the human-readable version in a trailing comment, and let Dependabot or Renovate maintain the pins. Both tools understand the uses: syntax for reusable workflows, not just actions.

secrets: inherit is a loaded gun

The convenient form passes every secret the caller has:

jobs:
  release:
    uses: yourorg/ci-workflows/.github/workflows/release.yml@<sha>
    secrets: inherit

That means the shared workflow — and every action inside it — can read all repository and organization secrets available to the caller, whether it needs them or not. A compromised transitive action in the shared workflow now exfiltrates the caller's entire secret set. Pass secrets explicitly instead, and treat any PR to a shared workflow that adds a new secret parameter as a security review event:

    secrets:
      registry_token: ${{ secrets.REGISTRY_TOKEN }}

Better yet, remove long-lived secrets from the equation: shared deploy workflows should authenticate to clouds via OIDC (id-token: write plus a trust policy scoped to repo, environment, and ref) so there's nothing durable to steal. And set permissions: explicitly at the top of every reusable workflow — contents: read as the baseline — because callers otherwise donate their default GITHUB_TOKEN scope, which in older orgs is still read-write everything.

Treat the workflows repo as production

At scale — say 40 shared workflows called by 400 repos — the ci-workflows repository is among the most privileged codebases you own. Minimum bar:

  • CODEOWNERS routing every change through the platform/security team, with branch protection requiring review and blocking force pushes. Tag moves should be impossible or alarmed.
  • Environment gates for the dangerous ones. Deploy workflows reference GitHub environments with required reviewers, so even a compromised caller can't self-approve production credentials.
  • Static analysis in the repo's own CI. zizmor catches the classic Actions footguns — expression injection from untrusted inputs into run: blocks, cache poisoning setups, excessive permissions — and actionlint catches the rest. Ten seconds of CI, embarrassing findings the first run, near zero after.
  • Input hygiene. Reusable workflow inputs are attacker-influenced data in many trigger contexts. Never interpolate an input directly into a shell line; assign it to an env: var and reference the variable in the script, which turns injection into inert text.
  • A release process. Cut tags from reviewed commits, publish release notes, and give consumers a documented SHA to bump to. GitHub's immutable releases capability (in public preview this year) will eventually make tag-rewriting attacks structurally impossible; until it's GA, your process is the control.

Org policy: make the good path the only path

GitHub's org-level settings do heavy lifting people skip. Under Actions permissions, restrict runnable actions to an allowlist: your org, GitHub-authored actions, and an explicit list of vetted third parties — this single setting would have prevented most drive-by action compromises from executing in your org at all. Pair it with:

  • Repository rulesets with required workflows, which force designated repos to run your shared security workflow (scanning, provenance, gating) on every PR — inversion of the adoption problem: instead of asking 400 teams to call your workflow, the platform mandates it.
  • Runner segregation. Workflows touching production credentials run on dedicated runner groups; public-repo and fork-triggered workflows never share runners with them.
  • Disable or constrain pull_request_target across the org unless a repo has a reviewed, specific need. It remains the most reliable way to hand attackers privileged execution.

For the dependency side of CI — the actions and workflows themselves are third-party components with versions, maintainers, and CVEs — inventory them like any other dependency. An SCA platform that parses workflow files gives you the "which repos call the compromised thing" answer in minutes; during the tj-actions response, Safeguard customers ran exactly that query while others grepped org-wide clones at 2 a.m.

Rollout sequence for an existing org

  1. Turn on the org actions allowlist in audit-first mode; expand the list as teams surface needs. (Week 1)
  2. SHA-pin the shared workflows repo internals; add zizmor and actionlint to its CI. (Week 1-2)
  3. Move callers from @main and @v2 refs to SHA pins via a Renovate rule. (Weeks 2-4, automated)
  4. Replace secrets: inherit with explicit passing, workflow by workflow. (Ongoing; start with deploy workflows)
  5. Adopt required workflows via rulesets for the security baseline. (Month 2)

Each step is independently valuable, so partial completion still pays. The order front-loads the org-wide controls that don't require touching every repo.

Frequently asked questions

Do reusable workflows need SHA pinning if they're internal?

Yes. The threat isn't just malicious maintainers — it's any compromise of the workflows repo or a token with write access to it propagating instantly to all callers. A SHA pin converts "instant org-wide execution" into "no effect until each caller bumps," which is time to detect.

Is secrets: inherit ever acceptable?

For low-privilege workflows in repos with trivial secret sets, it's tolerable. For anything touching deploy credentials, signing keys, or registry tokens, explicit passing is the difference between a contained compromise and a full secret dump — and OIDC beats both by removing the standing secret.

What tooling audits our existing workflows fastest?

Run zizmor across the org for injection, permissions, and pinning findings, and actionlint for correctness. For continuous coverage, treat workflow files as dependency manifests in your SCA tool so new unpinned or unvetted actions surface as findings on the PR that introduces them.

How would immutable releases change this?

Once generally available, immutable releases prevent the tag-rewrite attack that powered incidents like tj-actions — a published tag's content becomes fixed. SHA pinning remains worthwhile afterward for provenance clarity, but the sharpest edge of tag mutability goes away.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.