💻 Coding
GitHub Actions Workflow Security Hardening Reviewer: pull_request_target Risks, Script Injection, Pinned Action SHAs, GITHUB_TOKEN Permissions, and Secret Exposure
Paste your workflow YAML files and get a security review that flags dangerous triggers, untrusted input inside run steps, unpinned third party actions, overly broad token permissions, and secrets reachable from fork pull requests, with a patched YAML for every finding.
0Reviews
Prompt
Act as a CI/CD security engineer who audits GitHub Actions workflows for supply chain and injection risks, explains each finding in terms a maintainer can act on, and returns patched YAML rather than general advice.
Inputs:
- Workflow files pasted in full, each with its path under .github/workflows: [WorkflowFiles]
- Repository context: public or private, who can open pull requests, whether forks run workflows, default branch protection: [RepoContext]
- Secrets and environments used, by name only (never values), and which environments require reviewers: [SecretsAndEnvironments]
- Third party actions you depend on and any org allow list: [ActionInventory]
- Deploy targets and cloud auth method (OIDC, long lived keys, deploy tokens): [DeployTargets]
- Output format: [Format]
Generate:
1. A trigger review: every on: trigger per workflow, with special attention to pull_request_target, workflow_run, and issue_comment, and whether untrusted fork code is checked out or executed with secrets available.
2. A script injection scan: every run step or github-script that interpolates attacker controlled context (pull request title or body, branch name, issue comment, commit message) directly with ${{ }}, with the fix of passing it through an env variable.
3. An action pinning table from ActionInventory and WorkflowFiles: action, current ref, risk (tag, branch, or full commit SHA), and the pin format to use with a version comment.
4. A permissions review: workflow and job level permissions blocks, the least privilege set each job actually needs, and a top level default of contents: read.
5. Secret exposure paths from SecretsAndEnvironments: secrets reachable from untrusted triggers, secrets echoed to logs, and environments that should require reviewers before deploy.
6. Checkout hardening: persist-credentials false where the job does not push, and no checkout of the pull request head in privileged workflows.
7. Deploy auth notes from DeployTargets: where OIDC can replace long lived keys, with the trust condition to scope it.
8. A findings table (ID, file, line or step name, severity, fix) followed by the patched YAML for each changed workflow.
Constraints:
- Review only what is pasted; mark anything not visible as [not shown].
- Never print or guess secret values. Do not claim a commit SHA is the right one; tell the maintainer to resolve it from the action's release.
- Concise, technical tone. No em dashes.Instructions
Replace every [bracket] with your details before running. Works on ChatGPT, Claude, and Gemini.
Generated Output
This image was generated using the prompt above.

Examples
Example Input
WorkflowFiles:
.github/workflows/pr-label.yml
on: pull_request_target
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { ref: ${{ github.event.pull_request.head.sha }} }
- run: npm ci && npm run lint
- run: echo "Title: ${{ github.event.pull_request.title }}"
- uses: someorg/auto-labeler@main
with: { token: ${{ secrets.GH_PAT }} }
.github/workflows/deploy.yml
on: push (main)
permissions: write-all
steps: checkout@v4, aws-actions/configure-aws-credentials@v4 with AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY secrets, npm run deploy
RepoContext: public open source library, anyone can fork and open PRs, main protected with 1 review
SecretsAndEnvironments: GH_PAT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, NPM_TOKEN; environment "production" exists with no reviewers
ActionInventory: actions/checkout, aws-actions/configure-aws-credentials, someorg/auto-labeler; no allow list
DeployTargets: S3 plus CloudFront via long lived IAM user keys
Format: findings table, then patched YAMLExample Output
FINDINGS
| ID | File | Step | Severity | Fix |
| F1 | pr-label.yml | checkout with head.sha under pull_request_target | Critical | Do not check out or run fork code in a pull_request_target job. Split into an unprivileged pull_request lint job and a privileged label job that never checks out PR code. |
| F2 | pr-label.yml | npm ci and npm run lint | Critical | Fork controlled package.json scripts run with GH_PAT in scope. Move to pull_request trigger. |
| F3 | pr-label.yml | echo PR title | High | Title is interpolated into the shell. Pass via env and quote. |
| F4 | pr-label.yml | someorg/auto-labeler@main | High | Branch ref can change under you. Pin to a full commit SHA with a version comment. |
| F5 | pr-label.yml | GH_PAT | High | Replace with GITHUB_TOKEN plus pull-requests: write. |
| F6 | deploy.yml | permissions: write-all | High | Set contents: read and id-token: write only. |
| F7 | deploy.yml | long lived AWS keys | Medium | Switch to OIDC with a role trust condition on repo and main branch. |
| F8 | deploy.yml | environment production has no reviewers | Medium | Add required reviewers and reference environment: production in the job. |
| F9 | both | checkout persists token | Low | persist-credentials: false. |
PATCHED: .github/workflows/pr-lint.yml (new, unprivileged)
on: pull_request
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-sha> # v4.x, resolve from release
with:
persist-credentials: false
- run: npm ci && npm run lint
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "Title: $PR_TITLE"
PATCHED: .github/workflows/pr-label.yml (privileged, no PR code)
on: pull_request_target
permissions:
contents: read
pull-requests: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: someorg/auto-labeler@<full-sha> # pin, resolve from release
with:
token: ${{ secrets.GITHUB_TOKEN }}
PATCHED: .github/workflows/deploy.yml
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@<full-sha> # v4.x
with:
persist-credentials: false
- uses: aws-actions/configure-aws-credentials@<full-sha> # v4.x
with:
role-to-assume: arn:aws:iam::[not shown]:role/gh-deploy
aws-region: [not shown]
- run: npm ci && npm run deploy
FOLLOW UPS
- Delete the IAM user keys and GH_PAT after the OIDC role works.
- IAM role trust: restrict the sub claim to repo:<owner>/<repo>:environment:production.
- Add required reviewers to the production environment.
- NPM_TOKEN is not used in the pasted files [not shown]; confirm where it is referenced.