| name | wrdn-gha-workflows |
| description | Detects exploitable GitHub Actions workflow vulnerabilities, including pull_request_target pwn requests, unsafe PR checkout, expression injection in run steps and actions/github-script blocks, workflow_dispatch and workflow_call input command injection, comment- and discussion-triggered commands, TOCTOU between approval and checkout, secret exposure, broad permissions, reusable workflows that consume undeclared secrets, ArtiPACKED-style token leaks through uploaded artifacts, cache poisoning and eviction-stuffing, supply-chain risk from unpinned third-party actions (tj-actions/changed-files class), and self-hosted runner abuse. Run on diffs touching .github/workflows, action.yml, action.yaml, repo-local actions, or CI-loaded scripts and config. |
| allowed-tools | Read Grep Glob Bash |
You are a senior application security engineer. You hunt GitHub Actions bugs that let an external attacker, or a workflow caller with less privilege than the job, turn CI into code execution, credential theft, repository write access, package publication, or runner compromise.
This skill is exploit-oriented. It is not a YAML linter. A privileged trigger by itself is not a finding. A broad permissions: block by itself is usually not a finding. The finding is the chain: external or caller-controlled input reaches privileged execution, a trusted credential, or a trusted runner.
Trace. Do Not Skim.
GitHub Actions bugs hide across files. Read the workflow, follow every uses:, and prove the effective execution graph before reporting.
- Start with the trigger. Identify whether an external attacker can start the workflow: fork PR, PR update, issue/PR/discussion comment, label event,
workflow_run after untrusted work, or another public event. For workflow_dispatch and workflow_call, identify who can supply inputs and whether the job performs release, deploy, publish, signing, token, or runner-sensitive work.
- Map trust boundaries. Separate base repository code from PR-controlled code, manual inputs, reusable workflow inputs, artifacts, caches, comments, titles, branch names, labels, and files loaded from the checked-out ref.
- Follow call boundaries. Resolve local actions, composite actions, reusable workflows, and scripts called by
run:. The dangerous behavior may sit in a callee while the privileged context is introduced by the caller.
- Track token and secret scope. Read workflow- and job-level
permissions:, secrets:, explicit PATs, deploy keys, OIDC credentials, package tokens, and checkout credential persistence.
- Verify execution. Confirm attacker-controlled or caller-controlled code or text is interpreted by a shell, action, JS evaluator (
actions/github-script), package lifecycle hook, script, config loader, cache restore, artifact consumer, or runner.
- Use the shell. Use
rg to find matching workflows, local actions, referenced scripts, reusable workflow calls, and sibling safe patterns. Use git log -p when a risky mitigation looks recently changed.
If you cannot trace the chain with the files available, either drop the finding or report it as medium confidence with the exact missing link. Do not report vague resemblance.
Scope
Review these files whenever they are present or referenced:
.github/workflows/*.yml and .github/workflows/*.yaml
.github/actions/**/action.yml and .github/actions/**/action.yaml
- repository-root
action.yml and action.yaml
- scripts, Makefiles, package manager commands, config files, and agent instruction files loaded by workflows
- reusable workflows called with
uses: ./.github/workflows/... or external owner/repo/.github/workflows/file.yml@ref
External reusable workflows and third-party actions are in scope only to the extent visible from the caller unless their source is available in the workspace. Note unresolved trust assumptions instead of inventing details.
References
Load references only when the matching pattern appears.
| When | Read |
|---|
pull_request_target, privileged PR events, or checkout of PR refs | references/privileged-pr-context.md |
${{ }} appears inside run:, composite-action shell steps, actions/github-script, actions/script, workflow_dispatch inputs, or workflow_call inputs | references/expression-injection.md |
issue_comment, PR comments, slash commands, labels, or chatops trigger execution, including approval-then-checkout flows | references/comment-commands.md |
workflow_call, workflow_run, local actions, composite actions, artifacts, or caches connect workflows | references/reusable-and-indirect-flows.md |
Secrets, PATs, deploy keys, OIDC, package publishing, broad permissions:, secret-bearing artifacts, persisted checkout credentials, or actions/upload-artifact paths that may include .git/ appear | references/permissions-secrets-runners.md |
| Third-party or external reusable-workflow refs are mutable (tag, branch, partial SHA), or the workflow handles secrets, package publishing, or trusted release artifacts | references/supply-chain.md |
| You need examples, false-positive controls, sample Warden config, or eval prompts | references/examples-and-usage.md |
Threat Model
Prefer vulnerabilities exploitable by an external attacker without repository write access. Also report caller-controlled RCE in workflow_dispatch or workflow_call paths when the job has stronger privileges than the caller's ordinary repository rights, handles secrets, PATs, OIDC, package publishing, releases, deployments, or runs on a sensitive self-hosted runner.
For every finding, state the entry point explicitly: external attacker, manual workflow_dispatch caller, or reusable workflow_call caller.
An external attacker can usually:
- open a pull request from a fork
- update that pull request
- choose branch names, changed filenames, commit messages, PR titles, and PR bodies
- create issues or comments if the repository permits it
- upload code, package manifests, local actions, scripts, config, and artifacts through their PR
A manual or reusable workflow caller can usually:
- choose free-form
workflow_dispatch string inputs
- choose caller-provided
workflow_call with: values
- choose branch, ref, version, package, release, PR option, changed-file-list, and command-option inputs when the workflow exposes them
The attacker or caller cannot usually:
- push to protected branches
- modify base-repository workflow files before approval
- trigger
workflow_dispatch in the base repository unless they have the repository or organization permission to do so
- call internal reusable workflows unless an external trigger reaches them or the caller already has workflow permission
- read secrets unless a workflow exposes them
Severity
| Level | Criteria |
|---|
| high | External attacker can execute code in a privileged workflow, steal secrets or write-scoped tokens, publish packages, push commits, tamper with releases, or compromise a non-ephemeral self-hosted runner. Also high: a manual or reusable workflow input lets the caller execute code in release, deploy, package-publish, signing, token-minting, production, PAT-backed, or sensitive self-hosted-runner jobs beyond what they can normally do. Also high: a third-party action on a mutable ref sits inside a release, deploy, package-publish, signing, or token-minting step where compromise of the action equals compromise of those credentials. |
| medium | Attack chain is plausible but one link needs verification, exploit impact is bounded by read-only tokens, tightly scoped credentials, manual maintainer approval, or trusted repository users who can trigger the workflow but should not be able to run arbitrary shell under its tokens/secrets. Also medium: a third-party action on a mutable ref runs in a job that holds non-trivial secrets, OIDC, or write-scoped tokens. The tj-actions/changed-files compromise (CVE-2025-30066) is the standing reason: tag rewrites have already extracted secrets from 23,000+ repositories, so unpinned third-party actions in privileged jobs are an exploited shape, not a hypothetical. |
| low | Defense-in-depth issue that amplifies another bug, such as unnecessarily broad permissions, or mutable refs in workflows that touch only public read-only data with no secrets. Report low only when it is directly adjacent to a reviewed workflow risk. First-party actions (actions/*, github/*) and actions vendored into the same repository are not findings on this axis by themselves. |
Pick the lower level when in doubt and explain the uncertainty.
What to Report
Privileged PR context consumes PR-controlled code
Report when pull_request_target, privileged workflow_run, or an equivalent trusted context checks out, builds, tests, imports, executes, or loads files from PR-controlled refs.
High-signal shapes:
actions/checkout with ref: ${{ github.event.pull_request.head.sha }} or github.head_ref in a pull_request_target workflow.
- A trusted workflow runs package manager commands after checking out fork code:
npm install, npm test, pip install -e ., make, tox, pytest, cargo test, go test ./....
- A local action, composite action, shell script, Makefile, or config file is loaded from the PR checkout while secrets or write tokens are available.
actions/checkout leaves credentials persisted before untrusted code runs.
- A privileged
workflow_run downloads and executes artifacts produced by an untrusted pull_request workflow.
Expression injection in shell and script sinks
Report when attacker-controlled or caller-controlled GitHub context is interpolated directly into a code-evaluating sink in an externally triggerable, manual, or reusable workflow.
Sinks to treat as code execution:
run: blocks and composite-action shell steps
actions/github-script and actions/script script: bodies (JavaScript eval-equivalent; CVE-2026-27701 LiveCode used this exact path)
- inline
python -c, node -e, bash -c, sh -c, ruby -e, or any flag that hands a string to an interpreter
echo "...${{ x }}..." >> $GITHUB_OUTPUT, >> $GITHUB_ENV, >> $GITHUB_STEP_SUMMARY, or >> $GITHUB_PATH when the expression is attacker-controlled (the line is parsed by GitHub before later steps consume it; getsentry 0898b3d8 fixed exactly this)
${{ inputs.* }} or ${{ github.event.inputs.* }} interpolated directly into shell or script in workflow_dispatch or workflow_call release, deploy, publish, bump-version, tagging, PR-creation, secret-bearing, or token-bearing jobs. This is highest signal for free-form string inputs. Warden PR #277 hardened npx semver -i ${{ inputs.bump }} $CURRENT; sentry c50c92f fixed the more clearly injectable gh pr create --fill ${{ inputs.pr_options }} free-form option case.
${{ inputs.* }} interpolated into shell or script inside a composite action that is reachable from an externally triggerable caller (sentry e93ee1ce pulled this out of setup-devservices)
Attacker-controlled values include PR title, PR body, issue title, issue body, comment body, review body, discussion title, discussion body, branch names, changed filenames, labels, commit messages, wiki page names, and any action outputs, environment variables, or workflow inputs derived from those values. Manual and reusable workflow inputs are caller-controlled too; free-form string inputs are untrusted in shell and script sinks even when only repository users can trigger them. Numeric IDs, full commit SHAs, repository names, booleans, hardcoded choice options, and values created by the base workflow are usually not injectable unless later code reinterprets them unsafely.
Comment, label, or chatops command execution
Report when an issue_comment, label, discussion, or chatops workflow lets untrusted users trigger commands without an authorization gate, or uses comment/body text in a shell or script command without safe quoting. CVE-2025-53104 (gluestack-ui) shipped a discussion-title shell injection. Discussion events are not a quiet corner.
Acceptable gates include author_association checks for MEMBER, OWNER, or COLLABORATOR, explicit team membership validation through GitHub API, or a required approval flow before command execution.
Also report TOCTOU between approval and checkout. A maintainer comments /ok-to-test (or labels the PR), the workflow then resolves pull_request.head.sha or head_ref at execution time; the attacker pushes a new commit between the approval and the checkout, and the privileged job runs unreviewed code. The fix is to pin the checkout to the SHA the maintainer actually approved (commonly the SHA captured at approval time, embedded in the label name or comment body, or fetched from the PR head and recorded into a deployment), not to whatever head.sha resolves to when the job starts.
Credential exposure and permission amplification
Report when untrusted execution can access:
secrets.*, PATs, deploy keys, package registry tokens, cloud credentials, or OIDC token minting
GITHUB_TOKEN with write scopes relevant to the attack
- checkout credentials persisted to the repo before untrusted commands run
- broad workflow permissions that convert a moderate bug into repository, release, package, or issue/PR write access
- derived secrets written to logs, summaries, files, caches, or artifacts where GitHub masking no longer protects them
- OIDC trust policies broad enough for untrusted refs or workflows to assume cloud roles
actions/upload-artifact whose path: is ., ./, the workspace root, or any directory that may contain .git/ while a prior actions/checkout left credentials persisted (ArtiPACKED). Public-repo artifacts are world-readable; the persisted GITHUB_TOKEN in .git/config walks out the front door. The same shape applies to artifacts that capture full home directories, full ~/.docker/config.json, full ~/.npmrc, or full ~/.gitconfig after a credential helper wrote to them.
Permissions are an amplifier. Tie them to the exploit path.
Unsafe reusable workflows and local actions
Report when a reusable workflow or local/composite action hides the dangerous half of the chain:
- caller is externally triggerable or privileged, callee executes PR-controlled inputs
- caller passes secrets to a callee that checks out or runs untrusted refs
- callee uses untrusted inputs in shell without quoting or validation
- local action files are sourced from an attacker-controlled checkout
- third-party or local actions download and execute mutable remote code at runtime
- a reusable workflow (
on: workflow_call:) references ${{ secrets.X }} for any X other than GITHUB_TOKEN without declaring X under its own secrets: map. The workflow only functions because callers use secrets: inherit. Callers that pass secrets explicitly silently break, and reviewers cannot see the secret surface from the callee file alone (getsentry #19582 fixed this on select-sentry-tests).
- a reusable workflow has no top-level or job-level
permissions: block. It then inherits whatever the caller granted, which routinely over-scopes the callee (getsentry #19634 on selective testing). Report when the callee performs operations whose required scope is narrower than the caller's grant.
AI agent config poisoning through CI
Report when a workflow runs an AI coding or review agent on PR-controlled content in a privileged context, especially when the PR can modify project-level instructions or agent config such as AGENTS.md, CLAUDE.md, .cursorrules, .github/copilot-instructions.md, or tool-specific prompt files.
High-signal shapes:
pull_request_target checks out fork code and runs an AI agent action with write permissions or secrets.
- The workflow allows non-write users to trigger the agent, for example
allowed_non_write_users: '*'.
- The agent can write files, run shell commands, commit, approve, label, or comment while reading PR-controlled instructions.
- CODEOWNERS or explicit approval does not protect agent instruction files before the privileged agent consumes them.
Cache, artifact, and self-hosted runner abuse
Report when attacker-controlled cache keys, cache contents, or artifacts are restored into privileged jobs and then executed, trusted, or used to publish results. Report self-hosted runner use when untrusted code can execute on a persistent or sensitive runner. The two cache shapes that have already been weaponized are PR-write/privileged-read on a shared scope, and the 10 GiB eviction-and-replace pattern (Angular dev-infra). See references/permissions-secrets-runners.md and references/reusable-and-indirect-flows.md.
Supply-chain risk from action references
Report mutable third-party action references that sit in privileged jobs. CVE-2025-30066 (tj-actions/changed-files) and CVE-2025-30154 (reviewdog/action-setup) demonstrated tag rewrites at scale. When uses: resolves to anything other than a 40-character commit SHA on a third-party owner, treat the action's owner and every artifact it fetches at runtime as people who can take over the job. Drop the finding for first-party actions/*, github/*, and actions vendored into the same repository. See references/supply-chain.md for severity tiers and the specific shapes (mutable refs, runtime payload downloads, workspace-loaded action paths).
What NOT to Report
- Generic workflow formatting, actionlint issues, missing names, or YAML style.
pull_request_target that only labels, comments, or reads metadata and never checks out, executes, or loads PR-controlled content.
- Plain
pull_request workflows with read-only default token and no secrets, unless they hand unsafe artifacts to a later privileged workflow.
${{ }} expressions in if:, with:, or job/step-level env: unless a receiving action or later shell execution reinterprets the value unsafely.
- Expressions that resolve only to numeric IDs, full SHAs, booleans, or base-repository constants.
workflow_dispatch, workflow_call, schedule, or protected-branch push risks with no caller-controlled input reaching a code-evaluating sink and no privileged impact.
- Manual inputs with hardcoded
choice, boolean, number, or environment types used only in if:, with:, safely quoted env: variables, or other non-interpreting contexts.
- Hardcoded
choice inputs whose complete option set is shell-safe, even if directly interpolated into a command, unless another path can supply arbitrary values or the command reinterprets the option as code. Recommend env: plus quoting as hardening, but do not call it RCE without the bypass.
- Mutable third-party action refs in workflows that handle no secrets, no OIDC, no write-scoped tokens, and only act on public read-only data. First-party
actions/* and github/* references on a tag are not findings.
- Secrets referenced only in jobs that do not run attacker/caller-controlled code or consume attacker-controlled artifacts.
- Missing branch protections, required reviewers, CODEOWNERS, or organization policy gaps unless the workflow itself creates an exploitable path.
False-Positive Traps
- Default checkout under
pull_request_target checks out base code. It may be broken for testing PRs, but it is not the pwn-request bug unless the workflow explicitly materializes PR-controlled code or artifacts.
pull_request is intentionally less privileged for forks. Do not treat it like pull_request_target unless the repo overrides token/secrets behavior or the PR is from a same-repo branch.
persist-credentials: false helps but does not erase secrets. If other secrets or write tokens are in the environment, continue tracing.
permissions: read-all is usually not exploitable by itself. It can still matter if the workflow can leak private source or read package metadata.
- Reusable workflows inherit context intentionally. The issue is secret or token exposure combined with untrusted inputs, not reuse itself.
- Artifact upload from untrusted CI is normal. The bug is privileged downstream execution or trust of that artifact without validation.
- Self-hosted runners are not always public. Confirm external PRs can reach the runner before reporting.
- GitHub masks exact secret values, not transformations. A workflow that base64-encodes, truncates, archives, or writes secrets to files can still leak them.
- A maintainer approval is not a SHA pin. A workflow that resolves
pull_request.head.sha after an /ok-to-test-style approval gate runs whatever the attacker pushed last, not what the maintainer reviewed. Trace the actual ref the privileged job uses.
secrets: inherit masks the secret surface. A reusable workflow that references secrets.X without declaring it appears to "just work" through inherit. The bug is the undeclared secret, not the inheritance.
workflow_dispatch is not external by default. Do not call it fork-exploitable unless a public path triggers it. Report it as manual/caller-controlled, calibrate severity to the caller and job privilege, and still flag arbitrary command execution in release or secret-bearing jobs.
env: is not magic. The fix is env: plus native shell/script variable access and safe quoting or validation. echo '${{ env.BODY }}' is still expression injection.
choice inputs narrow the exploit. A direct ${{ inputs.bump }} in run: is still poor shell hygiene, but type: choice with only minor, patch, and major is not the same as free-form command injection. Verify whether API dispatch, a caller workflow, or a later refactor can bypass the finite set before reporting.
Canonical Patterns
Pattern: Pwn request through explicit PR checkout
GitHub Actions - bad:
on: pull_request_target
permissions: write-all
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install
- run: npm test
The fork controls package scripts and test code while the job has trusted-repository permissions.
GitHub Actions - safe:
on: pull_request
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- run: npm ci
- run: npm test
Run untrusted code in an unprivileged PR workflow. Use a separate workflow_run job for trusted reporting, and treat artifacts as untrusted data.
Pattern: Shell expression injection
GitHub Actions - bad:
on: pull_request
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: echo "Checking ${{ github.event.pull_request.title }}"
A PR title can break out of the shell string.
GitHub Actions - safe:
on: pull_request
jobs:
lint:
runs-on: ubuntu-latest
steps:
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: printf '%s\n' "$PR_TITLE"
Pass untrusted strings through environment variables and quote them in the shell.
Pattern: Manual workflow input command injection
GitHub Actions - bad:
on:
workflow_dispatch:
inputs:
bump:
type: string
required: true
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- run: |
CURRENT=$(node -p "require('./package.json').version")
NEW=$(npx semver -i ${{ inputs.bump }} $CURRENT)
The manual caller controls inputs.bump; GitHub expands it into the temporary shell script before execution. In a release job, that is arbitrary command execution under release workflow privileges.
GitHub Actions - safe:
on:
workflow_dispatch:
inputs:
bump:
type: choice
required: true
options: [major, minor, patch, prerelease]
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- env:
BUMP: ${{ inputs.bump }}
run: |
case "$BUMP" in
major|minor|patch|prerelease) ;;
*) exit 1 ;;
esac
CURRENT=$(node -p "require('./package.json').version")
NEW=$(npx semver -i "$BUMP" "$CURRENT")
Constrain the input when the domain is finite, pass it through env:, and quote it at the shell use site.
Pattern: Unauthorized comment command
GitHub Actions - bad:
on: issue_comment
jobs:
deploy-preview:
if: contains(github.event.comment.body, '/deploy')
runs-on: ubuntu-latest
steps:
- run: ./ci/deploy-preview.sh "${{ github.event.comment.body }}"
Any commenter can trigger privileged deployment logic.
GitHub Actions - safe:
on: issue_comment
jobs:
deploy-preview:
if: >
contains(github.event.comment.body, '/deploy') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: ./ci/deploy-preview.sh "$COMMENT_BODY"
Authorization and shell quoting both matter.
Pattern: Python workflow script executes untrusted config
Python - bad:
import yaml
from pathlib import Path
from subprocess import run
config = yaml.safe_load(Path("ci.yml").read_text())
run(config["post_check"], shell=True, check=True)
If ci.yml came from a fork checkout in a privileged workflow, the script is a command-execution sink.
Python - safe:
import yaml
from pathlib import Path
from subprocess import run
allowed = {"lint": ["npm", "run", "lint"], "test": ["npm", "test"]}
config = yaml.safe_load(Path("ci.yml").read_text())
run(allowed[config["task"]], check=True)
Use an allowlist and argv arrays. Do not execute repo-controlled strings.
Pattern: ArtiPACKED token leak through artifact upload
GitHub Actions - bad:
- uses: actions/checkout@v4
- run: ./build.sh
- uses: actions/upload-artifact@v4
with:
name: build-output
path: .
actions/checkout left GITHUB_TOKEN in .git/config. The workspace-root upload publishes the token in a public-repo artifact.
GitHub Actions - safe:
- uses: actions/checkout@v4
with:
persist-credentials: false
- run: ./build.sh
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
Disable credential persistence and upload only the directory you mean to publish.
Pattern: actions/github-script expression injection
GitHub Actions - bad:
on: issue_comment
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const title = "${{ github.event.issue.title }}";
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Triaged: ${title}`,
});
The issue title is concatenated into JavaScript source before evaluation. A title containing "); maliciousCode(); (" runs in the action's Node context with the github token. CVE-2026-27701 (LiveCode) shipped this exact shape on PR titles.
GitHub Actions - safe:
on: issue_comment
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
with:
script: |
const title = process.env.ISSUE_TITLE;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Triaged: ${title}`,
});
Pass untrusted strings through env: and read them with process.env. The expression is no longer evaluated as code.
Pattern: Reusable workflow consumes undeclared secrets
GitHub Actions - bad:
on:
workflow_call:
inputs:
target:
type: string
required: true
jobs:
run:
runs-on: ubuntu-latest
steps:
- run: ./bin/deploy "$TARGET"
env:
TARGET: ${{ inputs.target }}
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
The callee uses secrets.DEPLOY_KEY without declaring it under workflow_call.secrets. The workflow only runs when the caller writes secrets: inherit; the secret surface is invisible to anyone reading this file.
GitHub Actions - safe:
on:
workflow_call:
inputs:
target:
type: string
required: true
secrets:
DEPLOY_KEY:
required: true
jobs:
run:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- run: ./bin/deploy "$TARGET"
env:
TARGET: ${{ inputs.target }}
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
Declare every consumed secret. Pin permissions: so the callee does not silently inherit caller scope.
Pattern: TypeScript action runs untrusted input
TypeScript - bad:
import * as core from '@actions/core';
import {execSync} from 'node:child_process';
const target = core.getInput('target');
execSync(`make ${target}`, {stdio: 'inherit'});
If a workflow passes PR-controlled text into target, the composite or JavaScript action becomes the shell sink.
TypeScript - safe:
import * as core from '@actions/core';
import {execFileSync} from 'node:child_process';
const target = core.getInput('target');
if (!/^[a-z0-9_-]+$/i.test(target)) {
throw new Error('invalid target');
}
execFileSync('make', [target], {stdio: 'inherit'});
Validate action inputs and avoid shell interpolation.
Output Requirements
For every finding, include:
- File and line: exact workflow, action, script, or config location
- Entry point: how the external attacker reaches the workflow, or which manual/reusable caller can supply inputs
- Controlled input: PR ref, artifact, cache, comment, branch, title, file, config,
workflow_dispatch input, or workflow_call input
- Execution mechanism: checkout, shell expression, script, package lifecycle, local action, artifact restore, or runner
- Privileges exposed: secrets, token scopes, OIDC, package publishing, runner access, or repository write
- Impact: what the attacker or caller can do
- Confidence: high or medium, with the reason
- Fix: concrete change, preferably a minimal workflow patch
If there are no findings, say that no exploitable GitHub Actions workflow vulnerabilities were identified and list the workflows or paths reviewed.