From 921d31b8fe39995c4aae7334350e7c71af2980f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 21:28:38 +0200 Subject: [PATCH] fix(withings): cap WithingsApiError message at 1024 chars A misbehaving Withings upstream that returns a multi-MB error body could bloat any AuditLog.details row that captured the thrown message. Apply the cap inside the constructor so every downstream audit, notification, and pg-boss retry path inherits the bound without touching call sites. Closes the v1.4.42 L-1 carry-over. A regression test asserts the 1024-char cap for a 4000-char upstreamError while preserving the "Withings error: " prefix the legacy regex parser depends on. --- .../__tests__/response-classifier.test.ts | 17 +++++++++++++++++ src/lib/withings/response-classifier.ts | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib/withings/__tests__/response-classifier.test.ts b/src/lib/withings/__tests__/response-classifier.test.ts index 93ff0226d..0fbebcb04 100644 --- a/src/lib/withings/__tests__/response-classifier.test.ts +++ b/src/lib/withings/__tests__/response-classifier.test.ts @@ -174,6 +174,23 @@ describe("WithingsApiError", () => { }); expect(err.message).toBe("Withings subscribe error: 293"); }); + + // v1.4.43 W3-SECURITY (H-2, v1.4.42 L-1 carry-over) — the constructor + // caps the message at 1024 chars so a misbehaving upstream cannot + // bloat an `AuditLog.details` row through any downstream wrapper. + it("caps the message at 1024 chars when upstreamError is unbounded", () => { + const huge = "x".repeat(4000); + const err = new WithingsApiError({ + verb: "measure", + classification: "transient", + withingsStatus: 503, + reason: "withings_503", + upstreamError: huge, + }); + expect(err.message.length).toBe(1024); + // Prefix preserved so the legacy regex parser still matches. + expect(err.message.startsWith("Withings measure error: 503 - ")).toBe(true); + }); }); describe("classifyError", () => { diff --git a/src/lib/withings/response-classifier.ts b/src/lib/withings/response-classifier.ts index 210885515..4836dfdb2 100644 --- a/src/lib/withings/response-classifier.ts +++ b/src/lib/withings/response-classifier.ts @@ -246,7 +246,13 @@ export class WithingsApiError extends Error { const statusLabel = typeof opts.withingsStatus === "number" ? opts.withingsStatus : "?"; const errSegment = opts.upstreamError ? ` - ${opts.upstreamError}` : ""; - super(`Withings ${opts.verb} error: ${statusLabel}${errSegment}`); + // v1.4.43 W3-SECURITY (H-2, v1.4.42 L-1 carry-over): cap the + // message at 1024 chars in the constructor so every downstream + // audit / notification path inherits the bound. A misbehaving + // upstream that returns a multi-MB error body must not be able to + // bloat an `AuditLog.details` row. + const raw = `Withings ${opts.verb} error: ${statusLabel}${errSegment}`; + super(raw.slice(0, 1024)); this.name = "WithingsApiError"; this.verb = opts.verb; this.classification = opts.classification;