From 84cb93d10ac708c99ca2892f0ec0c90c192f9fab Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Sun, 25 Oct 2026 04:44:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(07-05):=20pay=20IN-01/IN-02/IN-03=20?= =?UTF-8?q?=E2=80=94=20dead=20export=20gone,=20wrangler=20comment=20truthf?= =?UTF-8?q?ul,=20900=20s=20boundary=20pinned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IN-01: delete the never-constructed ParsedInitData export (sole repo hit was its own declaration); the three read-helper catches now carry Intent: comments stating malformed-vs-tampered precedence and the chat-degrade law - IN-02: wrangler.jsonc line 2 states the post-fact truth — the chat_sessions namespace id below is live, no re-creation - IN-03: new T4 vector 'age 900 s → ok:true (the boundary is INCLUSIVE)' pins the strictly-greater comparator's inclusive edge (T-07-08); suite 126/126 --- bot/src/security/initdata.ts | 18 +++++++----------- bot/test/initdata.test.mjs | 8 ++++++++ bot/wrangler.jsonc | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/bot/src/security/initdata.ts b/bot/src/security/initdata.ts index de2efc1..b01b750 100644 --- a/bot/src/security/initdata.ts +++ b/bot/src/security/initdata.ts @@ -56,15 +56,6 @@ export interface WebAppInitChat { photo_url?: string; } -/** Shape of a fully parsed initData payload (hash/signature excluded from - * rawFields — they are auth metadata, not payload fields). */ -export interface ParsedInitData { - user: WebAppInitUser; - chat?: WebAppInitChat; - authDate: number; - rawFields: Map; -} - export interface ValidateInitDataOptions { /** "Now" in seconds-since-epoch — injectable for deterministic tests. */ nowSeconds?: number; @@ -95,8 +86,9 @@ function parseFields(raw: string): Map { try { fields.set(key, decodeURIComponent(value)); } catch { - // Undecodable percent-escape — keep raw so HMAC compare fails ("tampered") - // rather than silently dropping the field. + // Intent: an undecodable percent-escape keeps the RAW value so the HMAC + // compare fails ("tampered") rather than silently dropping the field — + // malformed-vs-tampered precedence (D-14). fields.set(key, value); } } @@ -160,6 +152,8 @@ export async function validateInitData( user = parsed as WebAppInitUser; } } catch { + // Intent: a malformed user payload is the "malformed" class, not a + // tampered one — the field never reaches the HMAC path. user = undefined; } } @@ -209,6 +203,8 @@ export async function validateInitData( chat = parsedChat as WebAppInitChat; } } catch { + // Intent: a malformed chat payload degrades to "no chat context" (a + // private chat) — it never fails an otherwise-valid private-chat payload. chat = undefined; } } diff --git a/bot/test/initdata.test.mjs b/bot/test/initdata.test.mjs index 728022c..4536b6a 100644 --- a/bot/test/initdata.test.mjs +++ b/bot/test/initdata.test.mjs @@ -188,6 +188,14 @@ test("T4 freshness boundary: age 901 s → reason stale", async () => { assert.equal(res.reason, "stale"); }); +test("T4 freshness boundary: age 900 s → ok:true (the boundary is INCLUSIVE)", async () => { + const validateInitData = await loadValidator(); + const now = Math.floor(Date.now() / 1000); + const raw = signedUser(now - 900); + const res = await validateInitData(raw, TEST_TOKEN); + assert.equal(res.ok, true, `expected ok:true at exactly 900 s, got ${JSON.stringify(res)}`); +}); + /* ————— T5 — field order independence (RESEARCH Pitfall 1) ————— */ test("T5 fields arriving out of alphabetical order still validate", async () => { diff --git a/bot/wrangler.jsonc b/bot/wrangler.jsonc index 00c7f76..3737bcd 100644 --- a/bot/wrangler.jsonc +++ b/bot/wrangler.jsonc @@ -1,5 +1,5 @@ { - // KV namespace id is filled in plan 03 task 1 after `npx wrangler kv namespace create chat_sessions` + // chat_sessions namespace created via `npx wrangler kv namespace create chat_sessions` — the id below is live. "name": "araharah-bot", "main": "src/webhook.ts", "compatibility_date": "2026-10-22",