GitHub Actions study materials
GitHub Actions study resources

Fork PR Security: pull_request_target, Secret Leakage, and Action Pinning

GitHub Actions · Put your knowledge to work in context.

All case studies

Fork PR Security: pull_request_target, Secret Leakage, and Action Pinning

An open-source repository accepts contributions from forks. .github/workflows/pr.yml triggers on pull_request_target and does three things: (1) checks out ${{ github.event.pull_request.head.sha }} with actions/checkout, (2) runs npm ci && npm test using a repository secret NPM_TOKEN, and (3) comments the test summary using GITHUB_TOKEN with pull-requests: write. The workflow is pinned to actions/checkout@v4 and actions/setup-node@v4; a reusable workflow is called with uses: acme/ci/.github/workflows/node.yml@main. Last week a contributor's PR modified package.json and the workflow printed the NPM_TOKEN in logs. Maintainers want fork PRs to keep running tests and posting comments, but without exposing secrets or granting write access to untrusted code. The repository has 1,200 stars and 30-50 fork PRs per month. The npm test script runs pretest, which executes node scripts/setup.js from the PR. The pull_request_target workflow uses the base repository's workflow file, so changes to .github/workflows/pr.yml in a PR do not take effect until merge.

1. Explain the security flaw in the current `pull_request_target` workflow and propose a safe split into two workflows. Include event types, permissions, and checkout behavior.

Read the worked answer

pull_request_target runs in the context of the base repository, with access to base secrets and a read/write GITHUB_TOKEN (subject to permissions). The workflow then checks out the PR head and runs npm ci && npm test. That executes the contributor's pretest script with NPM_TOKEN in the environment and a write-capable token. This is equivalent to running untrusted code with repository privileges. Split into two workflows. The unprivileged pull_request workflow checks out the PR merge commit, runs npm ci --ignore-scripts where possible, then npm test with permissions: contents: read, no secrets, and no write token. The privileged pull_request_target workflow only reads PR metadata, applies labels, or posts a comment; it must not check out the head or run scripts. If comments need test output, have the unprivileged workflow upload an artifact and a workflow_run workflow in the base repo download and post it, treating the content as untrusted. Use environment approval for any job that needs secrets.

2. The `NPM_TOKEN` leaked in logs. What immediate remediation and preventive controls would you apply?

Read the worked answer

First revoke and rotate NPM_TOKEN immediately; assume it is compromised. Remove it from the fork-PR path entirely. If tests need private packages, use a read-only granular token scoped to the minimum packages, store it in an environment with required reviewers, and never expose it to pull_request from forks. Add permissions: contents: read at workflow level and grant write only in the metadata job. Audit logs for token use and publish an advisory if the token had publish rights. Prevent recurrence with secret scanning and push protection, branch protection on workflow files, and a policy that untrusted PR workflows cannot reference secrets. Do not rely on GitHub's log masking: a transformed value, base64, or split string can bypass masking. Use npm ci --ignore-scripts in untrusted jobs unless lifecycle scripts are required, and run those scripts in a sandbox with no credentials.

3. Evaluate the reusable workflow pinned to `@main` and the action tags `@v4`. What pinning policy would you recommend and why?

Read the worked answer

uses: acme/ci/.github/workflows/node.yml@main is a mutable reference. Whoever can push to acme/ci's main branch can change code that runs in your privileged workflow without any change in your repository. Pin the reusable workflow to a full 40-character commit SHA, and update it through a reviewed dependency process. The same applies to third-party actions. First-party actions/checkout@v4 and actions/setup-node@v4 are lower risk but still mutable tags; pinning to SHA is the strongest policy. Enable Dependabot for GitHub Actions so SHA pins receive update PRs. Add a required review for .github/workflows/** and a CODEOWNERS rule. Note that pull_request_target uses the base branch's workflow file, so a PR cannot directly modify the privileged workflow, but the reusable workflow at @main is outside that protection. Also set explicit permissions in the reusable workflow; the caller's permissions can only reduce, not elevate, the called workflow's token.