GitHub Actions now holds some workflow runs in public repositories when its systems identify them as potentially malicious. The run does not begin until a collaborator with write access reviews and approves it through an authenticated web session. GitHub applies the protection automatically on public repositories hosted on github.com.
The approval button is a last checkpoint, not proof that a run is safe. A compromised account may have changed the workflow, an action reference, a script invoked by CI, or a dependency installed during the job.
Quick answer
Before approval, review the triggering commit and every file it can cause the workflow to execute. Check permissions, secrets, event type, action references, shell commands, generated scripts, dependency changes, and artifact uploads. Reject the run if the change is unexpected or the reviewer cannot explain its access.
permissions:
contents: read
jobs:
test:
permissions:
contents: read
steps:
- uses: actions/checkout@<full-commit-sha>
- run: npm ci --ignore-scripts
- run: npm test
This is only a starting point. Some tests need additional permissions or lifecycle scripts, but those capabilities should be visible and justified.
Understand why the run was held
GitHub says recent supply-chain attacks have used compromised credentials to push malicious workflow changes that steal CI/CD credentials and continue into other systems. The hold creates time for a repository collaborator to examine the run before hosted infrastructure executes it.
Do not assume the changed file must live under .github/workflows. A trusted workflow can run a modified shell script, package script, Makefile, Dockerfile, test fixture, or custom action. Review the complete data and command path.
Compare the author, device or location signals available to your organization, commit signing status, branch, time, and normal release process. Contact the contributor through a known channel if the change is surprising.
Review the event and token boundary
Start with the trigger. pull_request, pull_request_target, push, workflow_run, and manual dispatch do not have the same trust model. Pay particular attention to workflows that execute untrusted pull-request code while using secrets or a write-capable token.
Inspect workflow-level and job-level permissions. A missing permissions block may inherit broader defaults than the reviewer expects. Check access to environments, deployment credentials, OIDC, package publishing, caches, and self-hosted runners.
An approval should never be used to make a dangerous pull_request_target pattern feel acceptable. Keep privileged metadata handling separate from execution of contributor-controlled code.
Inspect every executable reference
Review:
- changed
run:commands and multiline shell blocks; - action versions, especially tags that replaced full commit SHAs;
- custom actions stored elsewhere in the repository;
- package-lock and toolchain changes;
- scripts downloaded with
curl,wget, or package managers; - encoded commands, unusual redirects, and suppressed output;
- artifact uploads that could collect environment files.
If the workflow installs dependencies, inspect new packages and lifecycle scripts. A one-line dependency change can be the executable change that matters most.
Approve through two-person review
For release, deployment, signing, or secret-rich workflows, require one person to investigate and another to approve. The first reviewer can annotate why the run is expected and which access it needs. The approver verifies that evidence rather than starting from a blank screen.
Record the held-run URL, triggering commit, decision, reviewer, and any containment action. If the account appears compromised, revoke sessions and credentials before focusing on CI recovery.
GitHub currently applies the automatic hold to public repositories on github.com. GitHub Enterprise Server does not receive this protection, so GHES administrators need their own controls.
Reduce damage before the next alert
Keep default workflow permissions read-only, pin third-party actions by commit SHA, restrict environment secrets, use short-lived OIDC credentials, and separate public pull-request tests from privileged deployment jobs.
Use isolated runners for untrusted code. Clear or scope caches so a hostile run cannot poison trusted builds. Protect workflow files with CODEOWNERS and branch rules, but remember that referenced scripts need ownership too.
Primary references
- GitHub Actions suspicious workflow approval announcement
- GitHub Actions security hardening
- Secure use reference for GitHub Actions
The correct question is not “does GitHub let me approve this?” It is “can I explain every trust boundary this run crosses?” When the answer is no, leaving the run blocked is the safe result.
Discussion
What would you try, change, or challenge after reading this guide? Specific results and errors help the next reader.
Comments will load as you reach this section.