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:
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;
},