From 93694cacd0c13e786fcdd2045817c502ccba8012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 4 Jun 2026 02:20:40 +0200 Subject: [PATCH] fix(idempotency): scrub hls_ share-link tokens from cached bodies The body-content secret guard matched hlk_ / hlr_ / sk- but not the new hls_ clinician share-link prefix, so a response echoing a freshly minted share token could be persisted to the idempotency cache. Add hls_ to SECRET_PATTERN alongside the existing token prefixes. --- src/lib/__tests__/idempotency.test.ts | 1 + src/lib/idempotency.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/__tests__/idempotency.test.ts b/src/lib/__tests__/idempotency.test.ts index f3f491a4d..1ab4b1fb8 100644 --- a/src/lib/__tests__/idempotency.test.ts +++ b/src/lib/__tests__/idempotency.test.ts @@ -287,6 +287,7 @@ describe("withIdempotency body-content exclusion (P12)", () => { it.each([ ["hlk_ access token", '{"data":{"token":"hlk_abc123"},"error":null}'], ["hlr_ refresh token", '{"data":{"refresh":"hlr_xyz789"},"error":null}'], + ["hls_ share-link token", '{"data":{"link":"hls_def456"},"error":null}'], ["sk- OpenAI key", '{"data":{"echoed":"sk-1234567890"},"error":null}'], [ "sk-ant- Anthropic key", diff --git a/src/lib/idempotency.ts b/src/lib/idempotency.ts index f9c3a8852..6f4db9f56 100644 --- a/src/lib/idempotency.ts +++ b/src/lib/idempotency.ts @@ -224,12 +224,13 @@ export function withIdempotency< // with, but if a future caller forgets we refuse to leak. // `hlk_` = our access tokens // `hlr_` = our refresh tokens + // `hls_` = clinician share-link tokens (v1.11) // `sk-…` = OpenAI / Anthropic keys (full token form, not the // raw substring — a 422 body explaining "task-id…" // or any other word containing `sk-` would otherwise // silently break idempotency for benign retries). const SECRET_PATTERN = - /(?:\b(?:hlk_|hlr_)[A-Za-z0-9_-]+|\bsk-(?:ant-)?[A-Za-z0-9_-]{8,})/; + /(?:\b(?:hlk_|hlr_|hls_)[A-Za-z0-9_-]+|\bsk-(?:ant-)?[A-Za-z0-9_-]{8,})/; const cloned = response.clone(); const text = await cloned.text(); if (!SECRET_PATTERN.test(text)) {