Skip to content

Commit e1ab53e

Browse files
committed
chore(repo): use null-prototype bag in ucp totals extraction
1 parent c296227 commit e1ab53e

1 file changed

Lines changed: 16 additions & 12 deletions

File tree

packages/mappings/ucp/src/mapper.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,31 @@ function deriveOrderStatus(order: UcpOrder): 'completed' | 'partial' | 'processi
4949
}
5050

5151
/**
52-
* Extract totals by type from a UCP order. The inline literal guards before
53-
* each property assignment are the recognized data-flow barrier pattern for
54-
* preventing prototype-chain pollution from a remote-controlled `total.type`:
55-
* the assignment is reached only when `total.type` is a non-empty string and
56-
* is not one of the three built-in property names that could otherwise
57-
* pollute the prototype chain or shadow a built-in slot. The returned bag
58-
* is a normal object (Object.prototype) so JSON serialization, snapshot
59-
* tests, and downstream `Object.getPrototypeOf` / property-helper consumers
60-
* behave exactly as they did before this barrier was introduced.
52+
* Extract totals by type from a UCP order. Defense-in-depth against
53+
* prototype-chain pollution from a remote-controlled `total.type`:
54+
*
55+
* 1. Property writes go to a null-prototype bag (`Object.create(null)`)
56+
* so there is no prototype chain that a malicious key could reach.
57+
* 2. Inline literal guards still drop the three forbidden built-in
58+
* property names from the output before assignment, so the bag never
59+
* carries those keys even as own properties.
60+
* 3. The bag is materialized into a normal-prototype Object via a
61+
* JSON round-trip so the public payload shape preserves
62+
* `Object.getPrototypeOf === Object.prototype` semantics for
63+
* downstream consumers (JSON serialization, snapshot tests, and
64+
* property-helper consumers).
6165
*/
6266
function extractTotals(order: UcpOrder): Record<string, MinorUnits> {
63-
const result: Record<string, MinorUnits> = {};
67+
const bag: Record<string, MinorUnits> = Object.create(null);
6468

6569
for (const total of order.totals) {
6670
const key = total.type;
6771
if (typeof key !== 'string' || key.length === 0) continue;
6872
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
69-
result[key] = total.amount;
73+
bag[key] = total.amount;
7074
}
7175

72-
return result;
76+
return JSON.parse(JSON.stringify(bag)) as Record<string, MinorUnits>;
7377
}
7478

7579
/**

0 commit comments

Comments
 (0)