Dangerous-Workflow Check¶
Key Insight
Dangerous workflows can leak secrets or enable code injection attacks.
Dangerous-Workflow¶
Target: 10/10 by eliminating dangerous workflow patterns
What it checks: Workflows that could leak secrets, execute untrusted code, or be exploited for privilege escalation.
Why it matters: Pull requests from external contributors can inject malicious code into workflows. A single misconfigured workflow can leak GitHub tokens or secrets.
Dangerous Pattern 1: pull_request_target with Code Checkout¶
The vulnerability:
name: PR Check
on:
pull_request_target: # Runs with write permissions
jobs:
test:
permissions:
contents: write # Can push to protected branches!
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
ref: ${{ github.event.pull_request.head.sha }} # DANGEROUS!
- run: npm install # Executes attacker's package.json scripts!
- run: npm test # Executes attacker's test code!
Attack scenario:
- Attacker opens PR with malicious
package.jsonthat exfiltratesGITHUB_TOKENin postinstall script - Workflow runs with write permissions (because
pull_request_target) - Malicious script pushes backdoor to main branch or exfiltrates secrets
Scorecard result: Dangerous-Workflow 0/10
Safe Pattern: pull_request for Untrusted Code¶
name: PR Check
on:
pull_request: # Read-only by default
jobs:
test:
permissions:
contents: read # Minimal permissions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
# Automatically checks out PR head
- run: npm install
- run: npm test
Why safe:
pull_requesthas read-only permissions by default- Even if attacker exfiltrates
GITHUB_TOKEN, it can't write to repository - No access to repository secrets
When to Use pull_request_target¶
Valid use case: Commenting on PRs or updating PR status checks.
Safe pattern:
name: PR Comment
on:
pull_request_target:
jobs:
comment:
permissions:
pull-requests: write # Only PR comments, not repo write
runs-on: ubuntu-latest
steps:
# DO NOT checkout PR code
- run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body "Thank you for your contribution!"
env:
GH_TOKEN: ${{ github.token }}
Critical rules:
- Never checkout PR code (
actions/checkout) - Never install dependencies from PR
- Never run scripts from PR
- Only use for metadata operations (comments, labels, status checks)
Dangerous Pattern 2: Inline Scripts with Untrusted Input¶
The vulnerability:
Attack scenario:
Attacker creates PR with title:
Executed command:
Result: Secrets exfiltrated to attacker server.
Safe Pattern: Environment Variables¶
- name: Echo PR Title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "PR title: $PR_TITLE"
Why safe: Environment variables are not subject to shell injection. Special characters are escaped.
Dangerous Pattern 3: Secrets in Pull Request Workflows¶
The vulnerability:
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: echo "API Key: ${{ secrets.API_KEY }}" # LEAKED!
Attack scenario:
- Attacker opens PR
- Adds
cat $GITHUB_ENVto see environment - Workflow logs show all secrets in plaintext
Safe pattern: Never use secrets in workflows triggered by untrusted PRs.
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: npm test
# NO SECRETS used
If you need secrets for testing:
on:
push: # Only on trusted branches
branches: [main]
jobs:
integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: npm run integration-test
env:
API_KEY: ${{ secrets.API_KEY }} # Safe - only runs on main
Dangerous Pattern 4: Self-Hosted Runners on Public Repos¶
The vulnerability:
Attack scenario:
- Attacker opens PR to public repository
- Workflow runs on your internal self-hosted runner
- Attacker's code executes inside your network
- Lateral movement to internal systems
Safe pattern: Only use self-hosted runners on private repositories.
Workflow Audit Checklist¶
Run through these for every workflow:
- [ ]
pull_request_targetworkflows never checkout PR code - [ ] Untrusted input always passed via environment variables
- [ ] No secrets in PR workflows
- [ ] Self-hosted runners only on private repos
- [ ] Job-level permissions (not workflow-level)
- [ ] Minimal permissions per job
Troubleshooting¶
Need to run tests with secrets on PR¶
Solution: Two-stage workflow.
# Stage 1: PR validation (no secrets)
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: npm test # Unit tests, no secrets
---
# Stage 2: Integration tests (with secrets)
on:
push:
branches: [main]
jobs:
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: npm run integration-test
env:
API_KEY: ${{ secrets.API_KEY }}
Scorecard flagging safe pull_request_target workflow¶
Check: Are you checking out PR code? If yes, that's dangerous.
Check: Are you running PR scripts? If yes, that's dangerous.
If neither: This might be a false positive. Document the safe pattern in PR description.