fix(07-05): pay IN-01/IN-02/IN-03 — dead export gone, wrangler comment truthful, 900 s boundary pinned

- 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
This commit is contained in:
araharah-executor
2026-10-25 04:44:37 +08:00
parent dea6261deb
commit 84cb93d10a
3 changed files with 16 additions and 12 deletions
+7 -11
View File
@@ -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<string, string>;
}
export interface ValidateInitDataOptions {
/** "Now" in seconds-since-epoch — injectable for deterministic tests. */
nowSeconds?: number;
@@ -95,8 +86,9 @@ function parseFields(raw: string): Map<string, string> {
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;
}
}
+8
View File
@@ -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 () => {
+1 -1
View File
@@ -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",