GitHub Actions Now Holds Suspicious Workflow Runs Before They Touch Your Secrets
GitHub Actions Now Holds Suspicious Workflow Runs Before They Touch Your Secrets
On July 28, 2026, GitHub started automatically holding workflow runs it identifies as potentially malicious before they execute. If a run gets flagged, it sits in a pending state until a repository collaborator with write access reviews and approves it through an authenticated web session. No settings, no opt-in, no workflow file changes โ GitHub applies this to public repositories on github.com by default.
This is a direct response to a pattern that's caused real damage over the past couple of years: an attacker compromises a maintainer's credentials (leaked PAT, phished session token, reused password), pushes a workflow file that runs on a trigger like push or schedule, and that workflow exfiltrates whatever secrets and tokens the repo has configured. The tj-actions/changed-files compromise in early 2025 โ where a compromised GitHub Action leaked CI secrets across more than 23,000 repositories โ is the incident this class of attack gets named after, and it's not an isolated case.
Why this class of attack works
A GitHub Actions workflow runs with whatever the repository has granted it: the auto-generated GITHUB_TOKEN, any repository or organization secrets referenced in the workflow, and โ critically โ the ability to make outbound network requests. None of that requires special permissions beyond "someone with write access pushed this YAML file." That's the whole attack surface: get write access to a repo (or to an action it depends on), and you get a code-execution environment with access to whatever CI secrets that repo holds.
The usual entry points are:
- Compromised maintainer credentials. A leaked personal access token or a phished session cookie gives an attacker the same push rights as the real maintainer.
- A compromised upstream Action. If your workflow references
some-org/some-action@v3and that org's maintainer account gets compromised, the attacker can push a newv3tag with malicious code, and every consumer picks it up on their next run. - Malicious first-time-contributor workflow changes. GitHub already required approval for first-time contributors' workflow runs on fork PRs โ but that's a different, older protection than the one covered here, and it only covers the fork-PR trigger path, not a compromised-credentials push straight to the repo.
The new hold-for-approval protection specifically targets the first two: it's GitHub's own detection (heuristics it hasn't published in detail) looking at a workflow run before it starts and deciding it looks like a hijack rather than legitimate maintainer activity.
What actually happens when a run is held
- Something triggers a workflow run โ a push, a schedule, a
workflow_dispatch, whatever the workflow listens for. - GitHub's detection evaluates the run before any job starts executing.
- If it's flagged, the run is held in a pending state. No steps execute, no secrets are exposed, nothing in the workflow runs โ not even the checkout step.
- A repository collaborator with write access has to open the run in the Actions tab and explicitly approve it from an authenticated browser session before it proceeds.
- Once approved, the run continues normally from the start.
Two limitations matter for planning:
| Limitation | What it means for you |
|---|---|
| Public repos on github.com only | Private repos and GitHub Enterprise Server get no protection from this feature today |
| No published detection criteria | You can't predict or test what triggers a hold โ treat it as a backstop, not a control you can rely on |
| No API/webhook coverage confirmed at launch | Don't build automation around "waiting for the hold event" yet โ check current GitHub docs before assuming an API exists |
If your production or client work lives in private repositories, this protection currently does nothing for you. That's the single most important caveat in the whole announcement, and it's easy to miss if you only read the headline.
What to actually do about it
Because this is a GitHub-side detection with undocumented criteria, treat it as one layer, not the layer. The hardening steps below apply whether or not your repo is public, and they're the controls that actually determine your blast radius if a workflow does get compromised.
1. Scope GITHUB_TOKEN to the minimum it needs. Most workflows don't need write access to the whole repo. Set explicit permissions at the workflow or job level instead of relying on the (often overly broad) repository default:
1permissions:
2 contents: read
3 # only add what the job genuinely needs, per job if scopes differ
4 pull-requests: write
2. Pin third-party Actions to a commit SHA, not a floating tag. A tag like @v3 can be moved by whoever controls that repo โ including an attacker who compromised it. A SHA can't be silently repointed:
1# floating tag โ an upstream compromise updates this automatically
2- uses: some-org/some-action@v3
3
4# pinned to a commit โ immune to a retagged upstream release
5- uses: some-org/some-action@a1b2c3d4e5f6...
Renovate and Dependabot both support SHA-pinned Actions with a version comment, so you don't lose update visibility by doing this.
3. Require review on changes to .github/workflows/. A CODEOWNERS entry forces a second set of eyes on any workflow file change before merge, regardless of who has write access:
1# .github/CODEOWNERS
2/.github/workflows/ @your-org/platform-team
4. Prefer OIDC over long-lived cloud credentials. If your workflows deploy to AWS, Azure, or GCP, a stolen static access key or service principal secret works for an attacker until you rotate it. OpenID Connect federation issues a short-lived token scoped to that specific workflow run โ a compromised workflow gets a token that expires in minutes and can't be reused outside CI:
1permissions:
2 id-token: write
3 contents: read
4
5steps:
6 - uses: aws-actions/configure-aws-credentials@v4
7 with:
8 role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
9 aws-region: us-east-1
5. Rotate immediately if you suspect any compromise. Held-for-approval or not, if you see a workflow run you don't recognize โ rotate every secret that workflow had access to, not just the one you think was used. Assume broader exposure than you can prove.
Best practices
- Audit
permissions:blocks across your repo the same way you'd audit IAM policies โ least privilege, reviewed on a schedule, not set once and forgotten. - Treat any unexpected approval prompt (whether it's this new hold feature or the existing first-time-contributor approval) as a security event to investigate, not a formality to click through.
- Keep an inventory of every third-party Action your workflows reference, and check it against the SHA-pinning list quarterly โ new dependencies get added faster than audits happen.
- If you maintain a popular public Action, enable 2FA on the maintainer account and consider a bot account with scoped permissions for automated releases, rather than a personal account with org-wide access.
Common mistakes to avoid
- Assuming this feature covers private repos. It doesn't, as of the July 28, 2026 rollout. If your org runs mostly private repos, this changes nothing about your actual risk.
- Treating a held run as automatically safe once approved. Approve based on evidence (does this run correspond to a change you or a teammate actually made?), not because clicking approve makes CI green again.
- SHA-pinning your own repo's Actions but not your dependencies' transitive Actions. A composite Action you pin can itself call other Actions by floating tag โ check nested references, not just your top-level
uses:lines. - Relying on GitHub's detection instead of your own controls. GitHub hasn't published what triggers a hold, which means you can't verify coverage. Layer your own SHA-pinning and token-scoping regardless.
Troubleshooting
"A workflow run is stuck in pending and I don't see an approve button." Confirm you have write access to the repository โ only collaborators with write access can approve held runs, and you must be logged in via an authenticated web session (not just viewing a cached page or using a fine-grained PAT in a script).
"I can't tell if a held run is from this new protection or the older first-time-contributor fork-PR approval." Check the trigger. First-time-contributor approval applies specifically to fork pull request runs from contributors without prior approved runs. This newer protection applies more broadly to any run GitHub's detection flags as suspicious, regardless of trigger type โ check the run's context banner in the Actions UI for the specific reason given.
FAQ
Do I need to configure anything to get this protection? No. GitHub applies it automatically to public repositories on github.com. There's no setting to enable, and currently no way to disable it either.
Does this replace SHA-pinning or scoped tokens?
No โ it's a detection-based backstop with undisclosed criteria, not a guarantee. Keep pinning Actions and scoping GITHUB_TOKEN regardless; those are controls you can verify and rely on.
Will this slow down my normal CI runs? Only if a run gets flagged, which should be rare for normal development activity. Legitimate pushes from established maintainers shouldn't trigger a hold under typical use.
What about GitHub Enterprise Server? Not covered at launch. If you self-host, this protection doesn't apply to you yet โ check the GitHub Changelog for updates on GHES rollout.
Is this related to the actions/checkout v7 pwn request fix from earlier this year?
They address different attack vectors. The checkout v7 change closes a specific pull_request_target misconfiguration that lets fork code run with privileged access. This new protection is a broader, always-on detection layer for compromised-credential pushes and other suspicious run patterns. Both are worth understanding โ see the further reading below.
Key takeaways
| Aspect | Detail |
|---|---|
| What changed | GitHub auto-holds workflow runs flagged as potentially malicious, pending write-access approval |
| Rollout date | July 28, 2026 |
| Scope | Public repositories on github.com only โ not GHES, and private repos get no protection from this feature |
| Configuration required | None โ applied automatically, no opt-in or settings |
| What it doesn't replace | Token scoping, SHA-pinning Actions, CODEOWNERS on workflow files, OIDC for cloud credentials |
| Your action item | Audit permissions: blocks and floating-tag Action references this week, regardless of repo visibility |