Secure the pipeline

Handle untrusted input and forks

Treat branch names, issue text, pull request metadata, changed code, and artifacts as attacker-controlled.

Pull request workflows run untrusted code. Fork contributors do not need your production secrets to propose a change, but their branch can still run on your runners.

Treat every field from the event as attacker-controlled: branch names, titles, bodies, changed files, and any script they add in package.json.

Even trusted contributors can accidentally paste a secret into a pull request title. Treat metadata as data, not code.

Split untrusted execution from privileged work

A safe pattern for fork PRs:

on:
  pull_request:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

No production secrets. Read-only token. The fork code runs, but it cannot push to main or read PRODUCTION_API_KEY.

Deployment and secret-using steps live in a separate workflow triggered only on trusted refs:

on:
  push:
    branches: [main]

jobs:
  deploy:
    environment: production
    steps:
      - run: ./scripts/deploy.sh

Watch package scripts and test changes

A malicious PR can change npm test to exfiltrate env vars. Review diffs to workflow files and install scripts carefully.

For high-risk repos, use pull_request_target only with extreme caution. It runs in the base branch context and can expose secrets to fork code. Prefer pull_request plus a trusted follow-up workflow when possible.

Threat-model one malicious PR

Imagine a PR that:

  • renames a test to skip failure
  • adds a postinstall script
  • uploads a fake artifact
  • puts shell metacharacters in the title

Walk your workflow and note where each attack reaches secrets, write tokens, or production deploy.

Artifacts from fork jobs should not flow into trusted deploy workflows without review. Treat artifact contents like code.

Try this on your own project: open a draft PR from a fork (or a second clone) and confirm production secrets are not available to that run.

Lesson completed