diff --git a/packages/openconditions-contrib-client/package.json b/packages/openconditions-contrib-client/package.json index 9220c58d8..590f32e97 100644 --- a/packages/openconditions-contrib-client/package.json +++ b/packages/openconditions-contrib-client/package.json @@ -17,7 +17,7 @@ "check-types": "tsc --noEmit" }, "dependencies": { - "canonicalize": "3.0.0" + "canonicalize": "4.0.0" }, "devDependencies": { "@types/node": "^26.3.0", diff --git a/packages/openconditions-contrib-client/src/conformance.test.ts b/packages/openconditions-contrib-client/src/conformance.test.ts index 34437654e..4fe057740 100644 --- a/packages/openconditions-contrib-client/src/conformance.test.ts +++ b/packages/openconditions-contrib-client/src/conformance.test.ts @@ -54,6 +54,10 @@ describe("cross-repo conformance", () => { it("canonicalClaimBytes matches the pinned RFC 8785 hex", () => { expect(toHex(canonicalClaimBytes(FIXED_CLAIM))).toBe(PINNED_CLAIM_HEX); }); + + it("rejects invalid Unicode through the public TypeError contract", () => { + expect(() => canonicalClaimBytes({ note: "\ud800" })).toThrow(TypeError); + }); }); describe("round-trip signing", () => { diff --git a/packages/openconditions-contrib-client/src/jcs.ts b/packages/openconditions-contrib-client/src/jcs.ts index 298084c6b..7fac2fcce 100644 --- a/packages/openconditions-contrib-client/src/jcs.ts +++ b/packages/openconditions-contrib-client/src/jcs.ts @@ -15,8 +15,8 @@ const encoder = new TextEncoder(); * `canonicalize` package (the JCS reference implementation, EXACT version * @openconditions/contrib-core uses); nothing here is hand-rolled. * - * @throws TypeError when the value contains a non-finite number, is nested too - * deeply to canonicalize, or is not JSON-serializable at all + * @throws TypeError when the value contains a non-finite number or lone + * surrogate, is nested too deeply to canonicalize, or is not JSON-serializable at all * (undefined, function, symbol); Error on a circular reference. */ export function canonicalClaimBytes(claim: unknown): Uint8Array { @@ -30,6 +30,9 @@ export function canonicalClaimBytes(claim: unknown): Uint8Array { if (err instanceof Error && /NaN|Infinity/.test(err.message)) { throw new TypeError("canonicalClaimBytes: non-finite number in value"); } + if (err instanceof Error && /Lone surrogate/.test(err.message)) { + throw new TypeError("canonicalClaimBytes: lone surrogate in value"); + } throw err; } if (text === undefined) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7248e857e..936f1f006 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2348,8 +2348,8 @@ importers: packages/openconditions-contrib-client: dependencies: canonicalize: - specifier: 3.0.0 - version: 3.0.0 + specifier: 4.0.0 + version: 4.0.0 devDependencies: '@types/node': specifier: ^26.3.0 @@ -6847,8 +6847,8 @@ packages: caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} - canonicalize@3.0.0: - resolution: {integrity: sha512-yYLfHyDMIXRyRqsKBRLX023riFLpXY2YOfdtqKXZRZy9qsfOJ9U+4F9YZL7MEzL5+ziN2x2nlBvY/Voi3EBljA==} + canonicalize@4.0.0: + resolution: {integrity: sha512-FEdXzwWs+N3rZqEqpqleiY9M1A6IAf9oo1zHFABnLW9FcJ/jzsu+G/Ks3Hq3FglmPKe80GeGBw8ZXLEnwPB0vQ==} engines: {node: '>=18'} hasBin: true @@ -9758,6 +9758,7 @@ packages: prom-client@15.1.3: resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} engines: {node: ^16 || ^18 || >=20} + deprecated: prom-client has been replaced by @prometheus-io/client promise@8.3.0: resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} @@ -15915,7 +15916,7 @@ snapshots: caniuse-lite@1.0.30001810: {} - canonicalize@3.0.0: {} + canonicalize@4.0.0: {} chai@6.2.2: {}