advanced 2 min answer

A CI/CD platform runs untrusted code from external contributors and also holds deployment credentials. How should secrets be architected so a malicious pull request cannot exfiltrate them?

secretsci-cduntrusted-codescopinggitlabdesign
Show the full answer Hide the answer

The core requirement

A build running untrusted code must never hold a credential that outlives it or reaches beyond its own job. Most serious CI breaches are credential-scope failures rather than sandbox escapes.

The design

1. Separate pipelines by trust level. Builds triggered by external contributions run in a restricted context with no access to deployment secrets. Only builds from trusted branches, after review, get credentials. This is the primary control and it is structural rather than technical.

2. Short-lived, narrowly-scoped credentials issued per job. Not a static secret in the environment. The job obtains a credential at start, scoped to exactly what it needs — one artefact repository path, one environment — and expiring in minutes.

3. Workload identity rather than stored secrets. The CI system proves the job's identity to the cloud provider and receives a short-lived token, so there is no long-lived credential to steal. This eliminates the highest-value target entirely and is the strongest available answer.

4. Approval gates between build and deploy. The stage that holds deployment credentials is separate from the stage that runs contributor code, with a human or a policy check between them.

5. Egress control on build runners. A compromised build with a credential still needs to send it somewhere. Restricting outbound network access to known destinations blocks the exfiltration step, which is a useful second line when the first fails.

6. Stronger isolation than containers for untrusted execution — lightweight virtual machines or sandboxed runtimes — since containers share a host kernel and kernel escapes are a real category.

The subtle leak paths

  • Secrets in build logs. Masking helps and is defeated by encoding or splitting the value. Assume logs from untrusted builds are public.
  • Secrets in cached artefacts or layers, persisting into subsequent builds.
  • Pull requests that modify the pipeline definition itself. If a contributor can change the workflow file and have it run with credentials, every other control is bypassed. Pipeline definitions for external contributions must come from the trusted branch, not from the contribution.
  • Environment variables visible to child processes, including anything the build invokes.
  • Shared runners retaining state between jobs from different trust levels.

That third point is the one most often missed and most often exploited: the pipeline definition is code, and treating it as data is the mistake.

The verification question

"If a contributor submits a pull request whose build prints every environment variable and uploads the working directory to an external host, what do they get?" The answer should be "nothing that matters", and if it is not, the trust separation is incomplete.