Files
source/crash-telemetry/test.mjs
T
c3e2d1ae3d Cap a report at 32 KB on both sides of the upload
The client would send up to 256 KB and the sink answered a settling 400 above
64 KB, so the two disagreed about what a valid report is, and the client
deletes what it is told is invalid. Unreachable in practice — the module table
and backtrace are both bounded, which puts the ceiling near 10 KB — but the
two constants have to agree for the disagreement not to matter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:25:47 -03:00

68 lines
2.7 KiB
JavaScript

// node test.mjs — exercises the status-code contract the client depends on.
import assert from "node:assert";
import worker from "./worker.js";
const REPORT = `
*** CRASH code=C0000005 eip=0071D5A0 esp=202BF99C ebp=202BFA18 ***
time 2026-07-26 10:41:02 UTC
build 6a941c06
handle @marco*evil
access violation: read from 00000002 [x](https://evil.test) \`
[0] 0071D5A0
[1] 006BE7DE
`;
let sent = null; // what we handed Discord on the last call
let upstream = () => new Response(null, { status: 204 });
globalThis.fetch = async (url, init) => { sent = init; return upstream(); };
let throttled = false;
const ENV = {
DISCORD_WEBHOOK: "https://discord.test/hook",
UPLOAD_LIMITER: { limit: async () => ({ success: !throttled }) },
};
const post = (body, env = ENV) =>
worker.fetch(new Request("https://x/", { method: "POST", body }), env);
// happy path
let r = await post(REPORT);
assert.equal(r.status, 204);
const content = JSON.parse(sent.body.get("payload_json")).content;
assert.match(content, /C0000005/);
assert.match(content, /read from 00000002/);
assert.match(content, /build `6a941c06`/);
assert.match(content, /marcoevil/); // markdown and @ stripped from the handle
// every field is attacker-chosen: no field may carry markup or a link into the
// channel, and none may close the backticks or bold the summary wraps it in.
assert.ok(!content.includes("]("), content); // no link syntax out of the report
assert.ok(!content.includes("://"), content); // and no bare URL either
assert.equal(content.match(/`/g).length, 4); // only the two pairs summarize() opens
assert.deepEqual(JSON.parse(sent.body.get("payload_json")).allowed_mentions, { parse: [] });
assert.equal(await sent.body.get("files[0]").text(), REPORT);
// junk: client should delete these, so they must be 400
assert.equal((await post("hello")).status, 400);
assert.equal((await post("x".repeat(32 * 1024 + 1))).status, 400);
// throttled: 429, and nothing reaches Discord. reportIsSettled() leaves 429
// unsettled, so the client keeps the report for next launch.
throttled = true;
sent = null;
assert.equal((await post(REPORT)).status, 429);
assert.equal(sent, null);
throttled = false;
// our failures: client must keep the report, so these must be 5xx
upstream = () => new Response(null, { status: 429 }); // Discord rate limit
assert.equal((await post(REPORT)).status, 503);
upstream = () => { throw new Error("network"); };
assert.equal((await post(REPORT)).status, 503);
upstream = () => new Response(null, { status: 204 });
assert.equal((await post(REPORT, {})).status, 503); // secret not set
// wrong method
assert.equal((await worker.fetch(new Request("https://x/"), {})).status, 405);
console.log("ok");