A crash report's strings reach the log on one line: anyone may post one, and a line break could otherwise forge another log line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric Wagoner
2026-10-16 17:06:24 -04:00
co-authored by Claude Opus 5.5
parent 956c908867
commit c984bb4b56
+9 -3
View File
@@ -117,6 +117,12 @@ const IMAGE_MAX_BYTES = 2_500_000;
const crashReportsPerAddress = new SlidingLimit(10, 60 * 60 * 1000); const crashReportsPerAddress = new SlidingLimit(10, 60 * 60 * 1000);
const CRASH_REPORT_MAX_BYTES = 64 * 1024; const CRASH_REPORT_MAX_BYTES = 64 * 1024;
/** Anyone may post a report, so its strings reach the log on one line,
* with no control characters to forge another. */
function printable(value: unknown, max: number): string {
return String(value ?? "").replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, max);
}
/** Chromium sends the Reporting API's crash reports here, after the fact, /** Chromium sends the Reporting API's crash reports here, after the fact,
* from the browser itself: a tab that crashes takes the page's own error * from the browser itself: a tab that crashes takes the page's own error
* reporting down with it. Each crash is logged and filed in Sentry as a * reporting down with it. Each crash is logged and filed in Sentry as a
@@ -139,10 +145,10 @@ function receiveBrowserReports(req: IncomingMessage, res: ServerResponse): void
if (!Array.isArray(reports)) return; if (!Array.isArray(reports)) return;
for (const r of reports as { type?: unknown; url?: unknown; user_agent?: unknown; body?: Record<string, unknown> }[]) { for (const r of reports as { type?: unknown; url?: unknown; user_agent?: unknown; body?: Record<string, unknown> }[]) {
if (r?.type !== "crash" || !crashReportsPerAddress.allow(address)) continue; if (r?.type !== "crash" || !crashReportsPerAddress.allow(address)) continue;
const url = String(r.url ?? "").slice(0, 200); const url = printable(r.url, 200);
const room = url.match(/\/join\/([A-Za-z0-9]{4})/)?.[1]?.toUpperCase() ?? "none"; const room = url.match(/\/join\/([A-Za-z0-9]{4})/)?.[1]?.toUpperCase() ?? "none";
const reason = typeof r.body?.reason === "string" ? r.body.reason.slice(0, 40) : "unknown"; const reason = typeof r.body?.reason === "string" ? printable(r.body.reason, 40) : "unknown";
const userAgent = String(r.user_agent ?? "").slice(0, 300); const userAgent = printable(r.user_agent, 300);
console.warn(`browser tab crashed: room ${room}, reason ${reason}, ${userAgent}`); console.warn(`browser tab crashed: room ${room}, reason ${reason}, ${userAgent}`);
Sentry.captureMessage("A player's browser tab crashed", { Sentry.captureMessage("A player's browser tab crashed", {
level: "warning", level: "warning",