taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 287e605f23f4e75ebb9b6f296d66dc59947a9b54
parent a3c05d81871fc6cbf003fcdb71b3464dba2c4b4b
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 01:24:33 +0200

util: harden codecForMap and union decoding

codecForMap accepted arrays, since typeof [] === "object", and assigned
decoded values with map[key], so a "__proto__" key reached the prototype
setter.  The union codec raised a TypeError rather than a DecodingError for
null.

Diffstat:
Mpackages/taler-util/src/codec.ts | 20++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts @@ -300,6 +300,11 @@ class UnionCodecBuilder< path: [`(${objectDisplayName})`], }; } + if (typeof x !== "object" || x === null || Array.isArray(x)) { + throw new DecodingError( + `expected object for ${objectDisplayName} at ${renderContext(c)}`, + ); + } const d = x[discriminator]; if (d === undefined && !alternatives.has(d)) { throw new DecodingError( @@ -359,12 +364,19 @@ export function codecForMap<T>( } return { decode(x: any, c?: Context): { [x: string]: T } { - const map: { [x: string]: T } = {}; - if (typeof x !== "object" || x === null) { + if (typeof x !== "object" || x === null || Array.isArray(x)) { throw new DecodingError(`expected object at ${renderContext(c)}`); } - for (const i in x) { - map[i] = innerCodec.decode(x[i], joinContext(c, `[${i}]`)); + const map: { [x: string]: T } = {}; + for (const i of Object.keys(x)) { + // Define rather than assign: assigning "__proto__" invokes the + // Object.prototype setter instead of creating an own property. + Object.defineProperty(map, i, { + value: innerCodec.decode(x[i], joinContext(c, `[${i}]`)), + writable: true, + enumerable: true, + configurable: true, + }); } return map; },