@@ -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 */
6266function 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