Dangerous Event
GitHub Actions runs workflow code from trusted workflow definitions on the default branch for most events. The security question is: who can start a run, what untrusted data (titles, bodies, branch names, artifacts) reaches the job, and what token and secrets it receives. Some combinations—externally controllable events plus secrets, write defaults, and unsafe use of user-controlled fields in run: steps or expressions—are how many public-repo compromises start.
High-risk trigger families
| Trigger or family | Typical abuse / what to watch for | Related ActSense docs |
|---|---|---|
pull_request_target | Runs with the base repo token; checking out or executing the PR head (pull_request.head.sha, etc.) lets fork content run with maintainer-level access. | Insecure Pull Request Target, Unsafe Checkout, Unsafe Checkout Ref, Environment Bypass Risk |
pull_request | Restricted token for forks by default; lower secret exposure than pull_request_target, but branch names, titles, and bodies are still untrusted in expressions and shells. | Risky Context Usage, Script Injection, Shell Injection |
workflow_run | Downstream workflows run when another finishes; a compromised upstream can chain into deploys or jobs that trust parent artifacts without verification. | Artifact Exposure Risk (see also sections below) |
issues, issue_comment | Titles, bodies, and comments are attacker-controlled for anyone who can open or comment. | Risky Context Usage, Self Hosted Runner Issue Exposure (public repo + self-hosted) |
discussion, discussion_comment | Same as issues: untrusted text in discussions and replies. | Risky Context Usage |
fork, watch (and similar) | Low cost for an attacker to trigger; dangerous if the workflow does sensitive work on every run. Often pairs with Risky Context Usage or workflow_run chains. | Risky Context Usage |
workflow_call | Callers pass inputs; optional or unvalidated inputs can reach dangerous steps. | Unvalidated Workflow Input |
Self-hosted runners increase impact for most of the above. See Self Hosted Runner, Self Hosted Runner PR Exposure, and Self Hosted Runner Issue Exposure.
General mitigation patterns
- Match trigger to trust — Prefer
pull_requestto build and test fork code; usepull_request_targetonly in narrow patterns that never run fork code with elevated tokens (Insecure Pull Request Target). - Treat payloads as hostile — Use environment variables, quoting, and allow-lists; see Risky Context Usage and Code Injection via Input.
- Tighten chains — For
workflow_run, filter by workflow, branch, and conclusion; verify artifacts (sections below and Artifact Exposure Risk). - Least privilege — Default
permissionsto read-only where possible. - Pin actions — See No Hash Pinning and related supply-chain pages.
workflow_run dependency chains
Description
workflow_run triggers execute whenever another workflow finishes—meaning any compromised workflow can automatically launch the dependent job with its token and permissions. Without strict filtering, attackers can escalate privileges, trigger cascading deployments, or farm artifacts from trusted jobs. GitHub recommends using workflow_call for reusable logic and tightening filters when workflow_run is unavoidable. 1
Vulnerable Instance
- Workflow listens to
workflow_runfor any workflow in the repository. - No filtering on branches or workflow names.
- Dependent job runs deployments or publishes packages with elevated permissions.
name: Auto Deploy
on:
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.shIf the upstream CI workflow is compromised, it can finish with completed and automatically trigger this deployment job.
Mitigation Strategies
- Prefer
workflow_call
Convert reusable logic to callable workflows requiring explicit invocation. - Strict filters
Restrictworkflows,branches, and requiregithub.event.workflow_run.conclusion == 'success'. - Limit permissions
Setpermissionsper job to the minimum needed; avoid inheriting upstream scopes. - Validate upstream artifacts
Verify checksums or signatures before consuming artifacts produced by the triggering workflow. - Audit chains
Document which workflows can trigger others and review them periodically.
Secure Version
name: Auto Deploy (Safe)
on:
workflow_run:
- workflows: ["CI"]
+ workflows: ["Release Build"]
+ branches: [main]
types: [completed]
jobs:
deploy:
+ if: >
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.head_branch == 'main'
+ permissions:
+ contents: read
+ deployments: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ - name: Verify artifact signature
+ run: ./scripts/verify.sh "${{ github.event.workflow_run.id }}"
- name: Deploy
run: ./scripts/deploy.sh
Impact
| Dimension | Severity | Notes |
|---|---|---|
| Likelihood | Teams often chain workflows for releases without adding filters. | |
| Risk | Compromised upstream jobs can force deployments or exfiltrate secrets. | |
| Blast radius | Any downstream environment or release pipeline triggered by the workflow is affected. |
References
- GitHub Security Lab, “GitHub Actions: Preventing pwn requests,” https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
- GitHub Security Lab, “Untrusted input in GitHub Actions,” https://securitylab.github.com/research/github-actions-untrusted-input/
- GitHub Docs, “Events that trigger workflows,” https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- GitHub Docs, “Events that trigger workflows: workflow_run,” https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#workflow_run 1
- GitHub Docs, “Reusing workflows,” https://docs.github.com/actions/using-workflows/reusing-workflows
GitHub Docs, “Events that trigger workflows: workflow_run,” https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#workflow_run ↩︎ ↩︎