Skip to content

Security: DEP0190 — runWrangler uses shell:true with unescaped args (potential command injection) #1182

@bryan7238

Description

@bryan7238

Summary

runWrangler in packages/cloudflare/src/cli/commands/utils/run-wrangler.ts calls spawnSync with shell: true while passing flag+value pairs as single concatenated strings (e.g. `--env ${wranglerOpts.environment}`).

Node.js 22 added DEP0190 specifically to flag this pattern because with shell: true the args array is joined into a shell string without escaping — meaning a value containing shell metacharacters (;, &&, $(), backticks, etc.) can break out of the argument boundary and execute arbitrary commands.

Reproduction

Any opennextjs-cloudflare build or opennextjs-cloudflare deploy on Node 22+ prints:

(node:XXXXX) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true
can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.

Affected code

packages/cloudflare/src/cli/commands/utils/run-wrangler.ts (dist: dist/cli/commands/utils/run-wrangler.js):

spawnSync(options.packager, [
  options.packager === "bun" ? "x" : "exec",
  "wrangler",
  ...injectPassthroughFlagForArgs(options, [
    ...args,
    wranglerOpts.environment && `--env ${wranglerOpts.environment}`,  // ⚠️ single string
    wranglerOpts.configPath && `--config ${wranglerOpts.configPath}`,  // ⚠️ single string
    wranglerOpts.target === "remote" && "--remote",
    wranglerOpts.target === "local" && "--local",
  ].filter((v) => !!v)),
], {
  shell: true,  // ⚠️ no escaping
  ...
})

Fix

Remove shell: true and split each flag+value into separate array entries so they are passed directly to the process:

spawnSync(options.packager, [
  options.packager === "bun" ? "x" : "exec",
  "wrangler",
  ...injectPassthroughFlagForArgs(options, [
    ...args,
    ...(wranglerOpts.environment ? ["--env", wranglerOpts.environment] : []),
    ...(wranglerOpts.configPath ? ["--config", wranglerOpts.configPath] : []),
    ...(wranglerOpts.target === "remote" ? ["--remote"] : []),
    ...(wranglerOpts.target === "local" ? ["--local"] : []),
  ]),
], {
  shell: false,
  ...
})

This eliminates shell interpolation entirely. Each argument is passed verbatim to the process, making injection impossible regardless of the value content.

Note for Windows: if shell: true was load-bearing for resolving npm.cmd / yarn.cmd on Windows, the fix can conditionally set shell: process.platform === "win32" and still split the args — that way Windows gets shell resolution without the injection risk from value interpolation (since on Windows the values still originate from developer-controlled config, not external input).

Workaround

Users can apply the fix locally with patch-package while waiting for a release.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions