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
+6 -1
View File
@@ -7,7 +7,7 @@ const REPORT = `
time 2026-07-26 10:41:02 UTC
build 6a941c06
handle @marco*evil
access violation: read from 00000002
access violation: read from 00000002 [x](https://evil.test) \`
[0] 0071D5A0
[1] 006BE7DE
`;
@@ -33,6 +33,11 @@ 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);