fix(02-bot-skeleton-initdata-security): WR-01 cap /api/session body at 4 KB before JSON split (413, category-only logs)
This commit is contained in:
+33
-1
@@ -9,7 +9,10 @@
|
||||
* (T-02-03). The bot is instantiated per request because the token only
|
||||
* arrives with `env` (research-verified Workers pattern).
|
||||
* POST /api/session → the phase's security gate (T-02-01): the caller
|
||||
* posts a verbatim Telegram.WebApp.initData string; only an HMAC-validated,
|
||||
* posts a verbatim Telegram.WebApp.initData string — capped at 4 KB
|
||||
* (WR-01: over-cap bodies are rejected 413 before any parsing; real
|
||||
* initData is ≪ 1 KB, so this touches no legitimate client); only an
|
||||
* HMAC-validated,
|
||||
* fresh identity obtains a one-time opaque session token (D-06). Rotation
|
||||
* of any prior token for the chat is mintSession's own invariant (D-07) —
|
||||
* this endpoint adds no extra branch and no extra write path (T-02-06).
|
||||
@@ -42,11 +45,40 @@ export default {
|
||||
},
|
||||
} satisfies ExportedHandler<Env>;
|
||||
|
||||
/** initData is Telegram's verbatim WebApp string — a few hundred bytes on
|
||||
* the wire (research: known-good fixtures ≪ 1 KB). 4 KB is generous
|
||||
* headroom; anything beyond is abuse aimed at the isolate's memory/CPU
|
||||
* budget (WR-01) — unbounded, `raw.split("&")` would allocate millions of
|
||||
* entries per multi-MB request. */
|
||||
const MAX_INITDATA_CHARS = 4096;
|
||||
|
||||
/** Category-only 413 for over-cap bodies (WR-01) — the error carries the
|
||||
* D-14 category, nothing of the request payload. */
|
||||
function oversizeBody(): Response {
|
||||
return new Response(JSON.stringify({ error: "malformed" }), {
|
||||
status: 413,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
/** Phase-2 security gate: initData in, one-time opaque session token out —
|
||||
* nothing else; unvalidated identities get a category-only 401 (T-02-01). */
|
||||
async function handleSession(req: Request, env: Env): Promise<Response> {
|
||||
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
|
||||
// WR-01: reject over-cap bodies before any parse work. A declared
|
||||
// Content-Length lets us say no without buffering the stream; lying or
|
||||
// absent headers are covered by the post-read length check. The character
|
||||
// bound is what governs the `split("&")` allocation below — the DoS
|
||||
// surface this cap exists to close.
|
||||
if (Number(req.headers.get("content-length") ?? "0") > MAX_INITDATA_CHARS) {
|
||||
console.log("[initdata] rejected: malformed"); // category ONLY — no payload, no ids, no timestamps
|
||||
return oversizeBody();
|
||||
}
|
||||
const raw = await req.text(); // the verbatim Telegram.WebApp.initData string
|
||||
if (raw.length > MAX_INITDATA_CHARS) {
|
||||
console.log("[initdata] rejected: malformed"); // category ONLY — no payload, no ids, no timestamps
|
||||
return oversizeBody();
|
||||
}
|
||||
const result = await validateInitData(raw, env.BOT_TOKEN);
|
||||
if (!result.ok) {
|
||||
console.log(`[initdata] rejected: ${result.reason}`); // D-14: category ONLY — no payload, no ids, no timestamps
|
||||
|
||||
Reference in New Issue
Block a user