Visitar URL original
Improve fix CVE-2026-9358 (NVD) / SNYK-JS-POSTCSSSELECTORPARSER-16873… · postcss/postcss-selector-parser@b6330e5 · GitHub
Skip to content

Commit b6330e5

Browse files
committed
Improve fix CVE-2026-9358 (NVD) / SNYK-JS-POSTCSSSELECTORPARSER-16873882 (clone/walk)
1 parent 053e949 commit b6330e5

3 files changed

Lines changed: 33 additions & 7 deletions

File tree

‎src/__tests__/recursion.mjs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ ava('serializing a deeply nested AST throws instead of overflowing the stack', t
4747
t.false(error instanceof RangeError, 'should be a controlled error, not a stack overflow');
4848
});
4949

50+
ava('cloning a deeply nested AST throws instead of overflowing the stack', t => {
51+
const deep = buildDeepAst(1000);
52+
const error = t.throws(() => deep.clone(), {instanceOf: Error});
53+
t.false(error instanceof RangeError, 'should be a controlled error, not a stack overflow');
54+
});
55+
56+
ava('walking a deeply nested AST throws instead of overflowing the stack', t => {
57+
const deep = buildDeepAst(1000);
58+
const error = t.throws(() => deep.walk(() => {}), {instanceOf: Error});
59+
t.false(error instanceof RangeError, 'should be a controlled error, not a stack overflow');
60+
});
61+
5062
ava('maxNestingDepth option controls the limit in both directions', t => {
5163
const input = nest(40);
5264
// A low limit rejects it and reports the configured value...

‎src/selectors/container.js‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {resolveMaxNestingDepth} from '../util';
1+
import {resolveMaxNestingDepth, MAX_NESTING_DEPTH} from '../util';
22
import Node from './node';
33
import * as types from './types';
44

@@ -189,12 +189,19 @@ export default class Container extends Node {
189189
}
190190
}
191191

192-
walk (callback) {
192+
walk (callback, depth = 0) {
193+
// Bound recursion so a pathologically deep node tree raises a catchable
194+
// error instead of overflowing the call stack (CVE-2026-9358 / CWE-674).
195+
if (depth > MAX_NESTING_DEPTH) {
196+
throw new Error(
197+
`Cannot walk selector: nesting depth exceeds the maximum of ${MAX_NESTING_DEPTH}.`
198+
);
199+
}
193200
return this.each((node, i) => {
194201
let result = callback(node, i);
195202

196203
if (result !== false && node.length) {
197-
result = node.walk(callback);
204+
result = node.walk(callback, depth + 1);
198205
}
199206

200207
if (result === false) {

‎src/selectors/node.js‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import {ensureObject} from "../util";
1+
import {ensureObject, MAX_NESTING_DEPTH} from "../util";
22

3-
let cloneNode = function (obj, parent) {
3+
let cloneNode = function (obj, parent, depth = 0) {
4+
// Bound recursion so a pathologically deep node tree raises a catchable
5+
// error instead of overflowing the call stack (CVE-2026-9358 / CWE-674).
6+
if (depth > MAX_NESTING_DEPTH) {
7+
throw new Error(
8+
`Cannot clone selector: nesting depth exceeds the maximum of ${MAX_NESTING_DEPTH}.`
9+
);
10+
}
411
if (typeof obj !== 'object' || obj === null) {
512
return obj;
613
}
@@ -19,9 +26,9 @@ let cloneNode = function (obj, parent) {
1926
cloned[i] = parent;
2027
}
2128
} else if ( value instanceof Array ) {
22-
cloned[i] = value.map( j => cloneNode(j, cloned) );
29+
cloned[i] = value.map( j => cloneNode(j, cloned, depth + 1) );
2330
} else {
24-
cloned[i] = cloneNode(value, cloned);
31+
cloned[i] = cloneNode(value, cloned, depth + 1);
2532
}
2633
}
2734

0 commit comments

Comments
 (0)