Security advisory: Secrets in builds from forked pull requests

Summary

This notice is a best practices reminder of a known configuration capability in CircleCI projects.

If your CircleCI project has both Build forked pull requests and Pass secrets to builds from forked pull requests enabled, every secret in that project’s environment is readable by anyone who opens a PR from a fork of your repo. This post explains how to check, what to do, and how to decide whether the configuration is right for your project.

This is a supported configuration. Some teams enable it on purpose. We’re publishing this because attacks against CI/CD secrets have been a steady pattern across the industry over the past year, and customers should be able to make the call on their own configuration.

Are you affected?

Open Project Settings → Advanced for each project and check:

  • Build forked pull requests
  • Pass secrets to builds from forked pull requests

If both are on, every secret in that project’s environment is readable by anyone who opens a PR from a fork of your repo. If only the first is on, fork PRs build with no access to your env vars or contexts, and this advisory does not apply.

What’s at risk

Any secret that reaches the job environment: npm tokens, GitHub PATs, AWS access keys, GCP service account keys, signing keys, deploy tokens, third-party API keys. A CI build executes the config that triggered it. When fork PRs can access secrets, the attacker controls the config (it lives in their fork) and the build hands their config your secrets.

The risk is proportional to the scope of the secrets exposed. A read-only API key for a test service is one thing; a publish token for a package consumed by thousands of downstream projects is another.

Concrete Example: NPM_TOKEN

Project has NPM_TOKEN as an env var so CI can publish releases on tag pushes. With fork-PR secret-passing on, the token is in the environment of every fork PR build, even though only tag builds publish. An attacker forks the repo, opens a PR whose .circleci/config.yml exfiltrates NPM_TOKEN, and uses the token to publish malicious versions.

Fix: split publish into a separate workflow that runs only on tags. Keep NPM_TOKEN in a restricted context. Fork PRs no longer see the token, and the long-lived token is either gone or scoped to a workflow forks can’t trigger.

What to do

Turn off Pass secrets to builds from forked pull requests. Forks will still build; they just won’t see your project’s environment values. This is the right setting for most projects.

Sometimes it’s not feasible to turn this setting off. For projects that need sensitive credentials:

  1. Replace long-lived tokens with OIDC-issued, short-lived credentials scoped to jobs in your repository, where the integration supports it.
  2. Move remaining long-lived sensitive credentials into a restricted context.

Finally, rotate every secret previously reachable from untrusted fork PR builds. There’s no way to know retroactively whether a malicious PR has already extracted them.

Documentation

If you think you’ve already been exposed

If a project of yours has had this configuration on and the exposed secrets carry publish, deploy, or write scope, assume the secrets may have been read by an unauthorized party. Rotate them, audit recent uses (npm publishes, deploys, commits).

Contact CircleCI Support if you need help investigating.