Untrusted Jenkinsfiles and Supply Chain

Scenario: A fork changes the Jenkinsfile to exfiltrate environment variables during a privileged pull-request build.

The scariest sentence in this whole track: "the pipeline runs whatever the Jenkinsfile says"

Here's an uncomfortable but essential fact: if your Jenkins job builds pull requests from external, unreviewed contributors, and that pull request is allowed to modify the Jenkinsfile itself, then you are letting a stranger write a program that your infrastructure will execute. This isn't a hypothetical edge case - it's one of the most common real-world CI/CD security incidents.

Analogy: Imagine hiring a contractor to fix a small item on a form, but the form itself also happens to contain the master instructions for how your entire office processes every request that day. If the contractor is allowed to also edit those master instructions while "fixing" their one small item, you haven't just given them access to fix a form - you've given them the ability to rewrite your office's entire workflow, read every file on the shared desk, and act with your office's authority, all disguised as "just submitting a form." That's exactly what an untrusted pull request that can modify the Jenkinsfile represents.

The entire discipline in this section is about drawing a hard line between code you trust enough to run with real access, and code you don't - and never letting the second category quietly borrow the first category's privileges.

Pipeline example

pipeline { agent { label 'untrusted-ephemeral' }; stages { stage('PR test') { steps { sh './test.sh' } } } }

Pull requests can control executable inputs, not just code changes

A contribution can change shell scripts, tests, build configuration files, and very often its own Jenkinsfile. On a privileged agent, that contribution's code can read environment variables, files, a reachable Docker socket, cloud metadata endpoints, shared caches, nearby processes, and any internal network the agent can reach - all without needing your credentials directly, just by running as code on a machine that already has that access. Simply omitting withCredentials from a pipeline does not remove the agent's own ambient authority to reach those things - the danger isn't only "did we expose a credential," it's "what can code running on this machine reach at all." Treat fork contributions and unreviewed branches as hostile input by default, until a deliberate, trusted policy explicitly promotes them.

The practical defense: disposable, unprivileged, isolated

Run untrusted validation - building and testing a pull request - on disposable agents that carry no production credentials, use short-lived checkout identities, have restricted outbound network access, enforce resource quotas, and share no writable caches with trusted builds. Keep pull-request testing entirely separate from post-merge publication - a PR build should be able to prove "this code compiles and passes tests" without ever being in a position to publish a real release artifact. Some SCM plugins support loading a trusted Jenkinsfile from the target branch (the one that was actually reviewed and merged) while still testing the contribution's source code against it - if you use this pattern, document precisely which revision supplies the orchestration logic and which supplies the application files being tested, since mixing these up defeats the whole point.

Fork PR #88 - running on agent: pr-sandbox-3 (no credentials, egress restricted)
[Pipeline] { (PR test)
[Pipeline] sh
+ ./test.sh
118 passed, 0 failed
Finished: SUCCESS

A safe result here means exactly this: useful test evidence was produced, on an agent with no path to production credentials or systems - the PR author learns their tests pass, and your infrastructure never exposed anything it didn't have to.

Defend the supply chain, not only the secrets

Beyond direct credential theft, an attacker with code execution in a PR build could forge a fake, all-green test-report XML file, poison a shared dependency cache that a later trusted build might reuse, publish a malicious package under a trusted package name/coordinate, exhaust every available executor as a denial-of-service, or substitute a legitimate package with a malicious one during a build step. The mitigations mirror the threat: restrict who and what can sign, publish, and deploy to only reviewed, post-merge builds; promote immutable artifact digests that were created specifically in a trusted build context, never in a PR sandbox; sanitize any file paths a Pipeline constructs from external input; cap runtime and fan-out (how many parallel branches or jobs one trigger can spawn); verify webhook signatures cryptographically rather than trusting the payload at face value; and reject unusual or privileged parameter combinations arriving from an unauthorized trigger cause.

A pitfall: mistaking "no reported secrets" for "actually safe"

Log masking (from the previous section) reduces accidental disclosure after an honest mistake - it is not sandboxing, and it does nothing at all against a deliberately hostile Jenkinsfile that never tries to print a secret to the log in the first place, instead quietly uploading it somewhere external. Validate your actual isolation boundary the adversarial way: from inside a PR-triggered build, deliberately attempt to reach cloud metadata endpoints, attempt outbound network egress to an address you control, attempt to write into a cache a trusted build will later reuse, attempt to bind a credential you shouldn't have folder access to, and attempt to read a neighboring workspace. A genuinely safe pull-request pipeline produces useful test evidence with absolutely no route to production authority - not merely a clean-looking log with no obviously suspicious lines in it.

Goal: Practice the concrete behavior in jenkins-credentials-binding. Learn sections have no Jenkins terminal; the Jenkins lab opens the real shared service in a new full-screen tab inside your private folder.