Prototype Pollution

MEDIUM
vercel/next.js
Commit: 0d4df38b0204
Affected: All versions prior to 16.2.2 (before this commit)
2026-07-02 13:06 UTC

Description

This commit alters the segment explorer trie to use null-prototype objects for its children maps. The previous implementation used plain {} for child nodes, which can collide with Object.prototype properties (e.g., 'constructor', 'toString') when segment names are user-controlled. This change prevents those collisions by ensuring the internal maps do not inherit from Object.prototype, reducing the risk of prototype pollution or logic errors in the segment explorer when handling user-supplied segment names.

Commit Details

Author: Joseph

Date: 2026-07-02 12:35 UTC

Message:

fix: handle prototype-colliding segment names in segment explorer trie (#95403) Fixes: https://github.com/vercel/next.js/issues/95395 ```console let x = {} x['constructor']; // function x['toString']; // function x = Object.create(null); x['constructor']; // undefined x['toString'] // undefined ```

Triage Assessment

Vulnerability Type: Prototype Pollution

Confidence: MEDIUM

Reasoning:

The change prevents prototype pollution by using null-prototype objects for trie children, avoiding collisions with Object.prototype properties like 'constructor' or 'toString' when segment names are user-controlled. This mitigates a class of security issues related to prototype pollution in the segment explorer trie.

Verification Assessment

Vulnerability Type: Prototype Pollution

Confidence: MEDIUM

Affected Versions: All versions prior to 16.2.2 (before this commit)

Code Diff

diff --git a/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx b/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx index a4812bb49fe..8b05181b2f0 100644 --- a/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx +++ b/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx @@ -105,6 +105,85 @@ describe('Segment Explorer', () => { }) }) + test('handle segments that collide with Object.prototype members', () => { + insertSegmentNode( + createSegmentNode({ pagePath: '/constructor/page.js', type: 'page' }) + ) + insertSegmentNode( + createSegmentNode({ pagePath: '/toString/page.js', type: 'page' }) + ) + + const { result } = renderHook(useSegmentTree) + + expect(result.current).toEqual({ + children: { + '': { + children: { + constructor: { + children: { + 'page.js': { + children: {}, + value: { + pagePath: '/constructor/page.js', + type: 'page', + boundaryType: null, + setBoundaryType: expect.anything(), + }, + }, + }, + value: undefined, + }, + toString: { + children: { + 'page.js': { + children: {}, + value: { + pagePath: '/toString/page.js', + type: 'page', + boundaryType: null, + setBoundaryType: expect.anything(), + }, + }, + }, + value: undefined, + }, + }, + value: undefined, + }, + }, + value: undefined, + }) + + removeSegmentNode( + createSegmentNode({ pagePath: '/constructor/page.js', type: 'page' }) + ) + + expect(result.current).toEqual({ + children: { + '': { + children: { + toString: { + children: { + 'page.js': { + children: {}, + value: { + pagePath: '/toString/page.js', + type: 'page', + boundaryType: null, + setBoundaryType: expect.anything(), + }, + }, + }, + value: undefined, + }, + }, + value: undefined, + }, + }, + value: undefined, + }) + }) + test('remove node in the middle', () => { insertSegmentNode( createSegmentNode({ pagePath: '/a/b/@sidebar/page.js', type: 'page' }) diff --git a/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts b/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts index 8145fe3e827..99938333027 100644 --- a/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts +++ b/packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts @@ -62,9 +62,11 @@ function createTrie<Value = string>({ getCharacters?: (item: Value) => string[] compare?: (a: Value | undefined, b: Value | undefined) => boolean }): Trie<Value> { + // Null-prototype children so segment names that collide with + // Object.prototype members (e.g. "constructor", "toString") work correctly. let root: TrieNode<Value> = { value: undefined, - children: {}, + children: Object.create(null), } function markUpdated() { @@ -82,7 +84,7 @@ function createTrie<Value = string>({ currentNode.children[segment] = { value: undefined, // Skip value for intermediate nodes - children: {}, + children: Object.create(null), } } currentNode = currentNode.children[segment]
← Back to Alerts View on GitHub →