Skip to content

Commit 503176e

Browse files
alexander-akaitbjohansebas
authored andcommitted
fix: prevent prototype pollution in cli.processArguments (webpack#21057)
1 parent 87f35bf commit 503176e

4 files changed

Lines changed: 73 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Reject `__proto__`, `constructor` and `prototype` path segments in `cli.processArguments` to prevent prototype pollution.

lib/cli.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const webpackSchema =
2525
* @property {string} path the path in the config
2626
*/
2727

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

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

@@ -449,6 +449,14 @@ const cliAddedItems = new WeakMap();
449449

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

452+
/**
453+
* Whether a path segment would walk into the prototype chain.
454+
* @param {string} name path segment
455+
* @returns {boolean} true when the segment is unsafe to write through
456+
*/
457+
const isUnsafeKey = (name) =>
458+
name === "__proto__" || name === "constructor" || name === "prototype";
459+
452460
/**
453461
* Gets object and property.
454462
* @param {ObjectConfiguration} config configuration
@@ -465,6 +473,14 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => {
465473
for (const part of parts) {
466474
const isArray = part.endsWith("[]");
467475
const name = isArray ? part.slice(0, -2) : part;
476+
if (isUnsafeKey(name)) {
477+
return {
478+
problem: {
479+
type: "prototype-pollution-in-path",
480+
path: parts.slice(0, i).join(".")
481+
}
482+
};
483+
}
468484
let value = current[name];
469485
if (isArray) {
470486
if (value === undefined) {
@@ -511,6 +527,14 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => {
511527
current = value;
512528
i++;
513529
}
530+
if (isUnsafeKey(property.endsWith("[]") ? property.slice(0, -2) : property)) {
531+
return {
532+
problem: {
533+
type: "prototype-pollution-in-path",
534+
path: parts.join(".")
535+
}
536+
};
537+
}
514538
const value = current[property];
515539
if (property.endsWith("[]")) {
516540
const name = property.slice(0, -2);

test/Cli.basictest.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,48 @@ describe("Cli", () => {
550550
]
551551
`)
552552
);
553+
554+
for (const key of ["__proto__", "constructor", "prototype"]) {
555+
it(`should not pollute the prototype through a "${key}" path segment`, () => {
556+
const args = {
557+
"evil-flag": {
558+
configs: [
559+
{ type: "string", multiple: false, path: `${key}.polluted` }
560+
],
561+
simpleType: "string",
562+
multiple: false
563+
}
564+
};
565+
const config = {};
566+
const problems = processArguments(args, config, {
567+
"evil-flag": "PWNED"
568+
});
569+
570+
expect({}.polluted).toBeUndefined();
571+
expect(problems).toEqual([
572+
expect.objectContaining({ type: "prototype-pollution-in-path" })
573+
]);
574+
});
575+
576+
it(`should not pollute the prototype through a trailing "${key}" path segment`, () => {
577+
const args = {
578+
"evil-flag": {
579+
configs: [{ type: "string", multiple: false, path: key }],
580+
simpleType: "string",
581+
multiple: false
582+
}
583+
};
584+
const config = {};
585+
const problems = processArguments(args, config, {
586+
"evil-flag": "PWNED"
587+
});
588+
589+
expect({}.polluted).toBeUndefined();
590+
expect(problems).toEqual([
591+
expect.objectContaining({ type: "prototype-pollution-in-path" })
592+
]);
593+
});
594+
}
553595
});
554596

555597
describe("isColorSupported", () => {

types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18931,6 +18931,7 @@ type ProblemType =
1893118931
| "unknown-argument"
1893218932
| "unexpected-non-array-in-path"
1893318933
| "unexpected-non-object-in-path"
18934+
| "prototype-pollution-in-path"
1893418935
| "multiple-values-unexpected"
1893518936
| "invalid-value";
1893618937
declare interface ProcessAssetsAdditionalOptions {

0 commit comments

Comments
 (0)