skip to content

Your public repo builds fork pull requests in a job that can read the deploy secret. What threat does that create?

level: seniorimportance: must knowfreq 54%

answer

  1. who authored the code being executed
  2. tests and build scripts are code too
  3. the credential outlives the build
  4. exfiltration channels cannot be enumerated
  5. no secrets on the untrusted side

basics

~20 s

An anonymous contributor's first pull request becomes attacker-authored code running inside your trusted build with the production deploy credential in reach. The boundary between untrusted contribution and trusted build is in the wrong place: a secret-bearing job must not execute proposed changes.

solid answer

~50 s

The contributor is an anonymous external entity, and everything in their branch is content they author: application source, tests, build scripts, dependency manifests, often the pipeline definition itself. If the job that runs that content can also read the deploy credential, then untrusted input and production authority are on the same side of the boundary, and the attacker's shortest path is one line of egress in a test file. The dominant category is elevation of privilege, with information disclosure of the credential as the mechanism. I would not try to fix it with scanning or log masking — an attacker who controls the code controls the obfuscation and the exfiltration channel. The structural fix is to move the boundary: untrusted pre-merge builds run with no secrets and no deployment identity, and anything credentialed runs only after review, on the reviewed commit.

go deeper

for a junior

Be ready to say that a pull request from an outsider contains code you did not write, and that anything the build job can read, that code can read too. Naming the deploy credential as the thing worth stealing is the core recall.

for a middle

Explain the mechanics of how attacker code gets executed without anyone approving it: test files, build scripts, dependency install steps, and the pipeline definition read from the proposed branch. Be able to say why masking and scanning are weak controls.

for a senior

Show the two-lane structure you would actually build — an unprivileged pre-merge lane and a credentialed post-merge lane on the reviewed commit — and name the time-of-check-to-time-of-use gap in approval gates that do not pin a commit.

for a principal

Own the policy call across many repositories: whether external contribution is accepted at all, what a pre-merge lane is permitted to reach, and who is accountable when a team wires a credential into an untrusted job. The tradeoff is contributor friction against standing production authority.

## What the attacker actually controls On a public repository, opening a pull request is an unauthenticated action. The person on the other end is an external entity with no established trust, and the branch they propose is entirely theirs. When people picture this threat they picture malicious application source, and then reassure themselves that a reviewer would spot it. But the contributor's control is much wider than the code under review: - **Test code**, which the job executes by design - **Build scripts and task definitions**, which run before anyone looks at the result - **Dependency manifests and lockfiles**, which can point at a package the contributor controls, whose install step is itself code - **The pipeline definition**, if the CI system reads it from the proposed branch rather than from the trusted base Any one of these executes commands. The reassurance "we only run unit tests" describes what the job is *for*, not what it *can do*: a test runner is not a sandbox, it is a program that executes repository code with the job's full privileges and network access. ## Why the credential is the prize The artifact of that build is throwaway; nobody ships a fork's pull request. What is not throwaway is the identity in the job's environment. A standing deploy credential outlives the build, and with it the attacker gains what the pipeline has: the ability to change production without going through review at all. The theft is a single request to a host they control — or a print in a log they can read, or a DNS lookup with the value in the name, or an artifact they can download afterwards. Enumerating exfiltration channels is a losing game, which is the point: the mitigation cannot be at the channel. ## Reading it through STRIDE - **Elevation of privilege** (violating authorization) is the headline: an anonymous outsider ends up acting with deployment authority. - **Information disclosure** (confidentiality) is the mechanism — the credential and anything else in the job environment leaves. - **Tampering** (integrity) follows if that build can write to a shared cache or an artifact store that trusted builds later consume. - **Repudiation** matters afterwards: the resulting production change is attributed to the pipeline identity, not to the person who caused it. Rating this is not subtle. The attacker position is anonymous and unauthenticated, the skill required is low, and the consequence is authority over production — this is the one that goes at the top of the list. ## Fixes that do not work - **Scanning the diff for malicious code.** You are asking a heuristic to beat an author who can obfuscate freely, in a language of their choosing, across files nobody reviews carefully. - **Masking secrets in logs.** That defends one channel out of many, and only after the code already holds the value. - **Requiring signed commits.** Signing establishes who wrote it, which was never in doubt; a signed malicious commit executes exactly as well. - **Reviewing after the build.** The build already ran. Ordering is the whole point. ## The structural fix Move the boundary so that untrusted content and production authority are never on the same side: 1. **A pre-merge lane with nothing to steal.** Builds of proposed changes run with no secrets, no deployment identity, no write access to shared caches or artifact stores, and restricted egress. If it is compromised, the loss is compute. 2. **A post-merge lane that is trusted because the content was reviewed.** Anything credentialed runs on the merged commit, on infrastructure the contributor never touched. 3. **Pin what was reviewed.** If a human gate releases a build, it must build the exact commit that was reviewed. Approve-then-push-again is a straightforward time-of-check-to-time-of-use gap. 4. **Read the pipeline definition from the trusted base**, not from the proposed branch, so the change cannot rewrite the rules under which it is evaluated. 5. **Shrink the credential.** Even on the trusted side, the deploy identity should be scoped to what the job deploys and short-lived, so that a compromise later in the path is bounded. ## Where the same reasoning goes next The same boundary question applies to internal repositories with a different attacker: a long-lived shared runner that is not wiped between jobs puts a low-privilege developer from an unrelated team in reach of another team's workspace and the host's identity. The attacker changes from anonymous outsider to authenticated insider, the asset changes from one deploy role to source-code IP plus other teams' credentials, and the modelling lesson is the same — you drew a boundary the infrastructure does not implement.

  • The team insists the fork job only runs unit tests. Does that lower your rating?
    No. A test runner executes repository code with the job's privileges and network access; the contributor writes the tests, the build scripts and the dependency manifests. "Only tests" describes intent, not capability. The rating is driven by what the job can reach, which is the credential, not by what the job is nominally for.
  • A maintainer must label the pull request before CI runs. Is that boundary sufficient?
    It helps only if the review that precedes the label covers build-affecting content, and only if the build then pins the exact reviewed commit. Otherwise the contributor pushes again after the label and the credentialed job runs content nobody saw — a time-of-check-to-time-of-use gap dressed up as a gate.
  • Same repository, but the runner is a shared self-hosted host used by every team. What changes?
    The attacker becomes an authenticated developer from an unrelated team, and the assets become other teams' source and their deploy identities. A host that is not wiped between jobs means consecutive jobs share one trust zone: leftover files, caches and the host's own credential are all reachable, so the boundary you drew between jobs does not exist.
  • How would you bound the damage if the credential does leak despite all this?
    Scope it before it leaks. A deploy identity restricted to the one service it ships, valid for minutes rather than months and issued per run, turns a stolen credential into a narrow, expiring capability. Detection helps, but the model should not rest on noticing the theft in time.

saying these in an interview costs you the question

  • Claims diff scanning prevents the attack
  • Relies on log masking to protect the secret
  • Believes running only tests means running no attacker code
  • Rates it low because the attacker is 'just a contributor'
  • Proposes reviewing the change after the build already ran
  • Treats the throwaway artifact as the asset instead of the credential

context