VYPR
High severityOSV Advisory· Published Jan 20, 2026· Updated Jan 23, 2026

OS Command Injection in `wrangler pages deploy`

CVE-2026-0933

Description

SummaryA command injection vulnerability (CWE-78) has been found to exist in the wrangler pages deploy command. The issue occurs because the --commit-hash parameter is passed directly to a shell command without proper validation or sanitization, allowing an attacker with control of --commit-hash to execute arbitrary commands on the system running Wrangler.

Root causeThe commitHash variable, derived from user input via the --commit-hash CLI argument, is interpolated directly into a shell command using template literals (e.g.,  execSync(git show -s --format=%B ${commitHash})). Shell metacharacters are interpreted by the shell, enabling command execution.

ImpactThis vulnerability is generally hard to exploit, as it requires --commit-hash to be attacker controlled. The vulnerability primarily affects CI/CD environments where wrangler pages deploy is used in automated pipelines and the

--commit-hash parameter is populated from external, potentially untrusted sources. An attacker could exploit this to:

  • Run any shell command.
  • Exfiltrate environment variables.
  • Compromise the CI runner to install backdoors or modify build artifacts.

Credits Disclosed responsibly by kny4hacker.

Mitigation * Wrangler v4 users are requested to upgrade to Wrangler v4.59.1 or higher. * Wrangler v3 users are requested to upgrade to Wrangler v3.114.17 or higher. * Users on Wrangler v2 (EOL) should upgrade to a supported major version.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wranglernpm
>= 2.0.15, < 3.114.173.114.17
wranglernpm
>= 4.0.0, < 4.59.14.59.1

Affected products

1
  • Range: @cloudflare/chrome-devtools-patches@0.1.1, @cloudflare/chrome-devtools-patches@0.1.2, @cloudflare/chrome-devtools-patches@0.1.3, …

Patches

1
99b1f328a9af

fix: execute git commands in pages deploy safely (#11889)

https://github.com/cloudflare/workers-sdkemily-shenJan 13, 2026via ghsa
3 files changed · +126 2
  • .changeset/fix-pages-deploy-command-injection.md+7 0 added
    @@ -0,0 +1,7 @@
    +---
    +"wrangler": patch
    +---
    +
    +Use argument array when executing git commands with `wrangler pages deploy`
    +
    +Pass user provided values from `--commit-hash` safely to underlying git command.
    
  • packages/wrangler/src/pages/deploy.ts+7 2 modified
    @@ -1,4 +1,4 @@
    -import { execSync } from "node:child_process";
    +import { execFileSync, execSync } from "node:child_process";
     import { writeFile } from "node:fs/promises";
     import path from "node:path";
     import {
    @@ -361,7 +361,12 @@ export const pagesDeployCommand = createCommand({
     				}
     
     				if (!commitMessage) {
    -					commitMessage = execSync(`git show -s --format=%B ${commitHash}`)
    +					commitMessage = execFileSync("git", [
    +						"show",
    +						"-s",
    +						"--format=%B",
    +						commitHash,
    +					])
     						.toString()
     						.trim();
     				}
    
  • packages/wrangler/src/__tests__/pages/deploy.test.ts+112 0 modified
    @@ -5714,6 +5714,118 @@ and that at least one include rule is provided.
     		});
     	});
     
    +	describe("deploys with custom commit information", () => {
    +		it("should accept and send --commit-hash parameter", async () => {
    +			mkdirSync("public");
    +			writeFileSync("public/README.md", "# Test project");
    +
    +			mockGetUploadTokenRequest(
    +				"<<funfetti-auth-jwt>>",
    +				"some-account-id",
    +				"foo"
    +			);
    +
    +			let deploymentFormData: Record<string, unknown> | null = null;
    +
    +			msw.use(
    +				http.post(
    +					"*/pages/assets/check-missing",
    +					async () => {
    +						return HttpResponse.json(
    +							{
    +								success: true,
    +								errors: [],
    +								messages: [],
    +								result: [],
    +							},
    +							{ status: 200 }
    +						);
    +					},
    +					{ once: true }
    +				),
    +				http.post("*/pages/assets/upload", async () => {
    +					return HttpResponse.json(
    +						{
    +							success: true,
    +							errors: [],
    +							messages: [],
    +							result: null,
    +						},
    +						{ status: 200 }
    +					);
    +				}),
    +				http.get("*/accounts/:accountId/pages/projects/foo", async () => {
    +					return HttpResponse.json(
    +						{
    +							success: true,
    +							errors: [],
    +							messages: [],
    +							result: { deployment_configs: { production: {}, preview: {} } },
    +						},
    +						{ status: 200 }
    +					);
    +				}),
    +				http.post(
    +					"*/accounts/:accountId/pages/projects/foo/deployments",
    +					async ({ request }) => {
    +						const formData = await request.formData();
    +						const formDataObj: Record<string, unknown> = {};
    +						for (const [key, value] of formData.entries()) {
    +							formDataObj[key] = value;
    +						}
    +						deploymentFormData = formDataObj;
    +
    +						return HttpResponse.json(
    +							{
    +								success: true,
    +								errors: [],
    +								messages: [],
    +								result: {
    +									id: "123-456-789",
    +									url: "https://abcxyz.foo.pages.dev/",
    +								},
    +							},
    +							{ status: 200 }
    +						);
    +					},
    +					{ once: true }
    +				),
    +				http.get(
    +					"*/accounts/:accountId/pages/projects/foo/deployments/:deploymentId",
    +					async () => {
    +						return HttpResponse.json(
    +							{
    +								success: true,
    +								errors: [],
    +								messages: [],
    +								result: {
    +									id: "123-456-789",
    +									latest_stage: {
    +										name: "deploy",
    +										status: "success",
    +									},
    +								},
    +							},
    +							{ status: 200 }
    +						);
    +					}
    +				)
    +			);
    +
    +			await runWrangler(
    +				"pages deploy public --project-name=foo --commit-hash=abc123def456 --commit-message='Test commit'"
    +			);
    +
    +			// Verify the commit_hash was sent in the deployment request
    +			expect(deploymentFormData).not.toBeNull();
    +			expect(deploymentFormData).toHaveProperty("commit_hash", "abc123def456");
    +			expect(deploymentFormData).toHaveProperty(
    +				"commit_message",
    +				"Test commit"
    +			);
    +		});
    +	});
    +
     	describe("deploys using redirected configs", () => {
     		let fooProjectDetailsChecked = false;
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.