fix(security): discord bot SSRF/log-injection/DoS hardening + bump markdown-it/js-yaml

- ecc-bot.mjs: validate interaction id (snowflake) and token before building the
  callback fetch URL (clears CodeQL js/request-forgery #239/#240/#241); clamp the
  remote heartbeat_interval to [1s,10m] (js/resource-exhaustion #242); strip CR/LF
  from log args (js/log-injection #246).
- Bump transitive dev deps via overrides/resolutions to patch quadratic-complexity
  DoS: markdown-it >=14.2.0 (Dependabot #45/#46), js-yaml >=4.2.0 (#42/#43).
  Both lockfiles regenerated; npm reports 0 vulnerabilities.
This commit is contained in:
Affaan Mustafa
2026-06-18 19:50:15 -04:00
parent a03d63cba0
commit 5e4f5533d7
4 changed files with 161 additions and 80 deletions
+37 -3
View File
@@ -26,7 +26,35 @@ const REPO_URL = 'https://github.com/affaan-m/ECC';
const INVITE = process.env.DISCORD_INVITE || '';
const API = 'https://discord.com/api/v10';
const log = (...a) => console.log(new Date().toISOString(), ...a);
// Strip CR/LF from string args so Discord-payload-controlled values (command
// names, usernames) cannot forge or inject extra log lines (log injection).
const log = (...a) =>
console.log(new Date().toISOString(), ...a.map(x => (typeof x === 'string' ? x.replace(/[\r\n]+/g, ' ') : x)));
// Interaction ids are Discord snowflakes (numeric) and tokens are a bounded
// URL-safe set. Validate before building the callback URL so a malformed or
// hostile gateway payload cannot inject path segments / alter the request
// target (SSRF). The host is always the fixed API constant.
const SNOWFLAKE_RE = /^[0-9]{1,20}$/;
const INTERACTION_TOKEN_RE = /^[A-Za-z0-9._-]{1,255}$/;
function interactionCallbackUrl(interaction) {
const id = String(interaction?.id ?? '');
const token = String(interaction?.token ?? '');
if (!SNOWFLAKE_RE.test(id) || !INTERACTION_TOKEN_RE.test(token)) {
throw new Error('invalid interaction id/token');
}
return `${API}/interactions/${id}/${token}/callback`;
}
// Clamp a remote-supplied timer interval to a sane range so a hostile/bogus
// heartbeat_interval cannot spin a tight loop or hang the bot (resource
// exhaustion). Discord's real value is ~41250ms.
function clampHeartbeatInterval(value) {
const n = Number(value);
if (!Number.isFinite(n)) return 41250;
return Math.max(1000, Math.min(n, 600000));
}
// ---------- skill + docs lookup (local clone as the data source) ----------
@@ -148,7 +176,13 @@ const handlers = {
async function respond(interaction) {
const name = interaction.data?.name;
const handler = handlers[name];
const url = `${API}/interactions/${interaction.id}/${interaction.token}/callback`;
let url;
try {
url = interactionCallbackUrl(interaction);
} catch (err) {
log('rejected interaction', err.message);
return;
}
if (!handler) {
await fetch(url, {
method: 'POST',
@@ -192,7 +226,7 @@ async function main() {
if (msg.s) seq = msg.s;
switch (msg.op) {
case 10: { // HELLO
const interval = msg.d.heartbeat_interval;
const interval = clampHeartbeatInterval(msg.d.heartbeat_interval);
setTimeout(() => {
send({ op: 1, d: seq });
setInterval(() => {