skip to content

Fork Pull-Request Exposure

Some trigger events run a fork's code with the base repository's secrets and write token, and some deliberately do not. 'Outsiders cannot merge' is not the boundary people assume.

on this pageshow

questions

4

When a fork's pull request triggers CI, what decides whether that run can read the base repository's secrets?

level: juniorimportance: must knowfreq 70%

answer

  1. ask whose context the job runs in
  2. the event decides, not the contributor
  3. fork context: no secrets, read-only token
  4. one event family keeps base-repo credentials
  5. danger: credentialed run fetching fork code

basics

~20 s

The trigger event, and whose context it runs in. Runs in the fork's context get no repository secrets and a read-only token; a second family of events runs in the base repository's context with full secrets and a write token.

solid answer

~50 s

Two things decide it: which event started the run, and whose context that event runs the job in. The ordinary pull-request event runs in the fork's context, and the platform deliberately withholds the base repository's secrets and downgrades the automatic token to read-only, because the code being built is written by someone with no access to the repository. A second family of events runs the job in the *base* repository's context: the workflow definition is read from the base branch and the job receives the full secret set and a write-capable token. Many platforms bolt a human gate on top for first-time or outside contributors, holding the run until a maintainer releases it. Note what none of this depends on: who the contributor is, or whether the pull request can be merged. The dangerous configuration is a base-context run that then pulls in the fork's code.

go deeper

for a junior

Be ready to say that the platform withholds secrets from fork pull-request runs and gives them a read-only token, and that this is why a bot comment or coverage upload stops working on fork contributions.

for a middle

Explain the second event family that runs in the base repository's context with full secrets, why it exists, and why the pipeline definition coming from the base branch does not make the run safe.

for a senior

Show that you audit by asking which runs hold credentials and whether any of them touch contributor-controlled content, then move the credential rather than adding a human check in front of it.

for a principal

Own the policy: define which trust context each class of pipeline runs in across the estate, and decide what you are willing to break for outside contributors to keep credentials and untrusted code apart.

## The question behind the question A pull request from a fork is the one place where a complete stranger gets to put files onto your build machine. Whether that is merely annoying or a full credential compromise comes down to one thing: **what credentials were in scope when their content was processed.** CI platforms make that decision for you, and they make it per *event*, not per *person*. ## The two contexts Think of every pipeline run as executing "as" one of two repositories. **The fork's context.** The ordinary pull-request event builds the contributor's branch as the contributor. Platforms treat this as untrusted by construction: - repository secrets are not injected into the job; - the automatic API token the job receives is downgraded to read-only, so the job cannot push a commit, move a tag, publish a package or edit a comment; - deployment credentials scoped to protected environments are not reachable. This is why teams discover the problem at all: a coverage upload, a preview deployment or a bot comment that works fine on internal branches silently stops working on fork pull requests. **The base repository's context.** Because that breakage is real, platforms also offer a class of events that runs *the base repository's own* pipeline definition in response to pull-request activity — reading the workflow file from the default branch rather than from the contributor's branch, and granting the run the base repository's secrets and a write-capable token. The intent is narrow: let a trusted, unchangeable pipeline definition react to a pull request (label it, comment on it, size it) without ever executing the proposed change. The trap is that the definition being trusted is not the same as the *data and code* being trusted. The event payload — title, body, branch name, author, commit message — is written by the contributor. And nothing stops the pipeline from explicitly fetching the contributor's branch. Do that, and you have attacker-authored files on disk in a job holding your publish credentials. ## The third input: the human gate On top of the event, platforms usually offer an approval gate for contributors who are new or outside the organisation: the run is created but queued until a maintainer releases it. Treat this as a *rate limiter on stranger-triggered compute and a prompt to look at the diff*, not as an authorisation boundary. It gates a pull request, not a specific commit, and a pull request keeps moving. ## What each half actually protects | Property | Fork-context run | Base-context run | |---|---|---| | Pipeline definition comes from | the fork's branch | the base branch | | Repository secrets | withheld | present | | Automatic token | read-only | write-capable | | Executes contributor's code | yes, by design | only if the pipeline fetches it | Read the table as a rule: **the two safe rows are "untrusted code, no credentials" and "trusted code, credentials". Mixing them is the whole vulnerability class.** ## What still goes wrong in the safe half A credential-free fork-context run is not harmless — it is just not a credential compromise. Arbitrary contributor code still runs on your build machine, so it consumes compute, reaches whatever the machine's network position allows, and reads whatever a reused machine happened to leave behind. That is why untrusted runs belong on disposable, network-restricted capacity. But the *distinct* failure this question is about — an outsider walking away with your signing key or publish token — requires the credentialed context. ## How to get the legitimate work done The checks that broke are real: contributors want their coverage posted, their benchmark numbers commented, their preview environment built. The durable pattern is to split the work by trust rather than to move credentials toward untrusted code: 1. an **unprivileged job** builds and tests the fork's code with no secrets, and emits results as inert data (a file, a number, a report); 2. a separate **trusted run**, holding the credential, consumes that data and performs the privileged act — and treats every byte of it as untrusted text, never as something to execute or interpolate into a command. That inversion — data flows from untrusted to trusted, credentials never flow the other way — is the answer an interviewer is listening for. ## Answering it in an interview Say the deciding factor is the event and the context, not the contributor. Name the two behaviours (no secrets and a read-only token versus full secrets and a write token). Then, unprompted, name the misuse: a base-context run that checks out the fork's code. That last sentence is what separates someone who has read the docs from someone who has fixed the bug.

  • If the fork-context run has no secrets, what can an attacker still do inside it?
    Run arbitrary code on your build machine. That means burning your compute, reaching anything the machine's network position exposes, and reading whatever a long-lived reused machine still has on disk. It is not a credential compromise, but it is why untrusted pull-request work belongs on disposable, network-restricted capacity rather than on a machine that also builds releases.
  • Does a base-context run take its pipeline definition from the fork's branch?
    No — that is the point of the event: the definition comes from the base branch, so a contributor cannot edit the steps. This is exactly why teams over-trust it. The steps are trusted; the event payload and any code the steps choose to fetch are not. Trusted definition plus untrusted input is still an untrusted execution.
  • A check genuinely needs a secret to report on fork code. How do you run it?
    Split it. One job builds and tests the fork's code with no credentials and publishes its output as plain data. A second, trusted run holding the secret picks that data up and does the privileged part — posting, uploading, deploying — treating the data as untrusted text it never executes or interpolates into a shell command.

A visitor badge lets someone into the lobby but opens no doors. The mistake is not the badge; it is lending them your own badge so the front-desk script can print their name.

saying these in an interview costs you the question

  • Assumes fork pull requests can never reach secrets at all
  • Thinks review-before-merge protects the pipeline run
  • Believes a read-only token means no code executes
  • Confuses who wrote the code with which context runs it
  • Treats the approval gate as an authorisation boundary

context

open as a page

Your fork-PR pipeline holds the chart publish credential and checks out the fork's Helm chart to lint it. What has that handed an attacker?

level: middleimportance: must knowfreq 58%

basics

~20 s

Use of the publish credential. Once the fork's files are on the runner, almost any tool run over them becomes attacker-controlled execution inside a job that already holds the credential, so the identity that publishes your charts becomes theirs.

open as a page

You gate privileged fork-PR runs behind maintainer approval or a safe-to-test label. How can a contributor still get newer code run?

level: seniorimportance: should knowfreq 44%

basics

~20 s

By pushing after the human looks. Approval and labels attach to the pull request, not to a commit, so the job resolves the branch head when it starts, and a gate that survives later pushes blesses code nobody reviewed.

open as a page

Merge requests from a contracted partner's fork run on your internal-network CI runners. What do you change, and what do you accept?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

A contract is not a technical control. Run partner merge requests like any untrusted fork: no credentials, disposable runners, no internal network position. If they genuinely need internal access, bring them inside the repository where it can be scoped and audited.

open as a page