From 03e671faafbb842a734a768df26bc28d58a3a427 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Thu, 9 Jul 2026 16:53:16 +0100 Subject: [PATCH 1/2] fix(database): forward currentUrl and iframeUrl through session projection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `getSessionRecordBySessionId` narrowed the origin session read to a projection that omitted `currentUrl` and `iframeUrl`. `buildEscalation` in `submitPoWCaptchaSolution` then forwarded `undefined` into `createSession`, so every post-PoW escalated session was persisted without the URL context — 100% loss confirmed in prod on 2026-07-09 (384/384 Twickets escalations in a 30-minute window had `currentUrl: undefined` even though 1786/1786 origin PoW sessions had it populated). Add both fields to the projection and extend the regression test that already guards the buildEscalation contract. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/database/src/databases/provider.ts | 2 ++ .../sessionRecordProjection.integration.test.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/database/src/databases/provider.ts b/packages/database/src/databases/provider.ts index 496f100bf0..991486a24e 100644 --- a/packages/database/src/databases/provider.ts +++ b/packages/database/src/databases/provider.ts @@ -1661,6 +1661,8 @@ export class ProviderDatabase userSitekeyIpHash: 1, simdReadings: 1, dnsEvent: 1, + currentUrl: 1, + iframeUrl: 1, // captchaType is required by the peek-before-consume path // in `CaptchaManager.isValidRequest` — without it, every // escalation peek would compare `undefined !== ` diff --git a/packages/database/src/tests/integration/sessionRecordProjection.integration.test.ts b/packages/database/src/tests/integration/sessionRecordProjection.integration.test.ts index 303d907a38..46f7e50fe2 100644 --- a/packages/database/src/tests/integration/sessionRecordProjection.integration.test.ts +++ b/packages/database/src/tests/integration/sessionRecordProjection.integration.test.ts @@ -115,6 +115,8 @@ describe("getSessionRecordBySessionId projection", () => { decryptedHeadHash: "head-hash-xyz", siteKey: "site-key-1", reason: "user-passed", + currentUrl: "https://example.com/checkout", + iframeUrl: "https://widget.example.com/embed", headers: { "user-agent": "Mozilla/5.0", "accept-language": "en-GB", @@ -151,6 +153,13 @@ describe("getSessionRecordBySessionId projection", () => { expect(got.iFrame).toBe(false); expect(got.decryptedHeadHash).toBe("head-hash-xyz"); expect(got.reason).toBe("user-passed"); + // currentUrl / iframeUrl are forwarded onto the escalation session so + // downstream analytics (attack attribution, per-URL routing) survive + // the PoW → image/puzzle hop. Missed on the original projection + // (2026-07-09): 100% of escalation sessions were storing + // `currentUrl: undefined` in prod. + expect(got.currentUrl).toBe("https://example.com/checkout"); + expect(got.iframeUrl).toBe("https://widget.example.com/embed"); // Headers come back so `buildEscalation` can forward them onto the // escalation session, but the multi-KB `x-tls-clienthello` is From 5ecbbc0645856e169a4f38614c25aac882c6fd3d Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Thu, 9 Jul 2026 16:59:35 +0100 Subject: [PATCH 2/2] chore(changeset): add patch changeset for @prosopo/database Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/session-projection-current-iframe-url.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/session-projection-current-iframe-url.md diff --git a/.changeset/session-projection-current-iframe-url.md b/.changeset/session-projection-current-iframe-url.md new file mode 100644 index 0000000000..6dec478cbe --- /dev/null +++ b/.changeset/session-projection-current-iframe-url.md @@ -0,0 +1,5 @@ +--- +"@prosopo/database": patch +--- + +Include `currentUrl` and `iframeUrl` in the `getSessionRecordBySessionId` projection so `buildEscalation` forwards them onto the escalated session. Without this, every post-PoW routed session was persisted with `currentUrl: undefined`, dropping URL attribution on the PoW → image/puzzle hop.