Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix: prevent prototype pollution in cli.processArguments
Reject `__proto__`, `constructor` and `prototype` path segments when
walking the schema path so a crafted argument path can no longer write
through to Object.prototype.
  • Loading branch information
alexander-akait committed May 29, 2026
commit a71b49c698efd3408910cfac50f4521aab7ce7c1
5 changes: 5 additions & 0 deletions .changeset/cli-prototype-pollution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack": patch
---

Reject `__proto__`, `constructor` and `prototype` path segments in `cli.processArguments` to prevent prototype pollution.
26 changes: 25 additions & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const webpackSchema =
* @property {string} path the path in the config
*/

/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */
/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "prototype-pollution-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */

/** @typedef {string | number | boolean | RegExp} Value */

Expand Down Expand Up @@ -449,6 +449,14 @@ const cliAddedItems = new WeakMap();

/** @typedef {string | number} Property */

/**
* Whether a path segment would walk into the prototype chain.
* @param {string} name path segment
* @returns {boolean} true when the segment is unsafe to write through
*/
const isUnsafeKey = (name) =>
name === "__proto__" || name === "constructor" || name === "prototype";

/**
* Gets object and property.
* @param {ObjectConfiguration} config configuration
Expand All @@ -465,6 +473,14 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => {
for (const part of parts) {
const isArray = part.endsWith("[]");
const name = isArray ? part.slice(0, -2) : part;
if (isUnsafeKey(name)) {
return {
problem: {
type: "prototype-pollution-in-path",
path: parts.slice(0, i).join(".")
}
};
}
let value = current[name];
if (isArray) {
if (value === undefined) {
Expand Down Expand Up @@ -511,6 +527,14 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => {
current = value;
i++;
}
if (isUnsafeKey(property.endsWith("[]") ? property.slice(0, -2) : property)) {
return {
problem: {
type: "prototype-pollution-in-path",
path: parts.join(".")
}
};
}
const value = current[property];
if (property.endsWith("[]")) {
const name = property.slice(0, -2);
Expand Down
42 changes: 42 additions & 0 deletions test/Cli.basictest.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,48 @@ describe("Cli", () => {
]
`)
);

for (const key of ["__proto__", "constructor", "prototype"]) {
it(`should not pollute the prototype through a "${key}" path segment`, () => {
const args = {
"evil-flag": {
configs: [
{ type: "string", multiple: false, path: `${key}.polluted` }
],
simpleType: "string",
multiple: false
}
};
const config = {};
const problems = processArguments(args, config, {
"evil-flag": "PWNED"
});

expect({}.polluted).toBeUndefined();
expect(problems).toEqual([
expect.objectContaining({ type: "prototype-pollution-in-path" })
]);
});

it(`should not pollute the prototype through a trailing "${key}" path segment`, () => {
const args = {
"evil-flag": {
configs: [{ type: "string", multiple: false, path: key }],
simpleType: "string",
multiple: false
}
};
const config = {};
const problems = processArguments(args, config, {
"evil-flag": "PWNED"
});

expect({}.polluted).toBeUndefined();
expect(problems).toEqual([
expect.objectContaining({ type: "prototype-pollution-in-path" })
]);
});
}
});

describe("isColorSupported", () => {
Expand Down
1 change: 1 addition & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18931,6 +18931,7 @@ type ProblemType =
| "unknown-argument"
| "unexpected-non-array-in-path"
| "unexpected-non-object-in-path"
| "prototype-pollution-in-path"
| "multiple-values-unexpected"
| "invalid-value";
declare interface ProcessAssetsAdditionalOptions {
Expand Down
Loading