Sanitize every field the channel summary quotes, not just the handle

The endpoint is public and unauthenticated, so the whole uploaded file is
attacker-chosen, not only the part the client copied from Ja2.ini. The build
field sat inside backticks a backtick closes, and the access-violation text
went in raw, so either could carry markdown or a link into the channel.

One clean(): printable ASCII minus what Discord reads as markup or a URL,
length-capped. It replaces the handle's own stripping and absorbs the .trim()
the field getters did, which also drops the CR that "(.+)$" captures off a
CRLF report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
majcosta
2026-07-28 10:25:47 -03:00
committed by majcosta
co-authored by Claude Opus 5
parent 90f5bff69b
commit 1d0b4beb4c
3 changed files with 24 additions and 9 deletions
+12 -6
View File
@@ -15,17 +15,23 @@
const MAX_BYTES = 64 * 1024; // reports run 2-8 KB; anything near this is not ours
// Every field below is lifted out of the uploaded file, and the endpoint is public
// and unauthenticated: treat all of it as attacker-chosen, not just the handle.
// Keep printable ASCII minus everything Discord reads as markup or a link, and cap
// the length, so nothing in a report can shape the message around it.
const clean = (s) => (s || "").replace(/[^A-Za-z0-9 .,+-]/g, "").slice(0, 64).trim();
// Pull the header fields the client writes, for a one-line channel summary.
// See writeExceptionBacktrace(): "*** CRASH code=... ***", then " <key> <value>".
function summarize(text) {
const field = (name) => (text.match(new RegExp(`^\\s+${name} (.+)$`, "m")) || [])[1]?.trim();
const code = (text.match(/code=(\w+)/) || [])[1] || "????????";
const av = (text.match(/access violation: (.+)/) || [])[1]?.trim();
const field = (name) => (text.match(new RegExp(`^\\s+${name} (.+)$`, "m")) || [])[1];
const code = clean((text.match(/code=(\w+)/) || [])[1]) || "????????";
const av = clean((text.match(/access violation: (.+)/) || [])[1]);
const parts = [`\`${code}\`${av ? ` (${av})` : ""}`];
const build = field("build");
const handle = field("handle");
const build = clean(field("build"));
const handle = clean(field("handle"));
if (build) parts.push(`build \`${build}\``);
if (handle) parts.push(`from **${handle.replace(/[`*_~|@]/g, "")}**`); // no pings, no markdown
if (handle) parts.push(`from **${handle}**`);
return parts.join(" · ");
}