From cfad568029d520a424bf8b70f3ee9761ac0ce5ac Mon Sep 17 00:00:00 2001 From: EJClaw Date: Sat, 15 Aug 2026 19:28:14 +0900 Subject: [PATCH] fix(dave): hard RUN_MS time-box ceiling armed at startup, not on ready gate The RUN_MS auto-leave was only armed inside announceReady(), which requires a fully successful join (DAVE/MLS op29/op30 -> mlsReady). When the E2EE handshake stalls after op26 key_package, announceReady never fires, so the "time-boxed" selfbot ran unbounded in a live channel. Arm RUN_MS at process startup instead, independent of handshake state, and make leaveAndExit idempotent so the ceiling, ready timer, and signal handlers can't double-fire. Co-Authored-By: Claude Opus 4.7 --- dave/join.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dave/join.mjs b/dave/join.mjs index 566748f..00ff134 100644 --- a/dave/join.mjs +++ b/dave/join.mjs @@ -46,7 +46,10 @@ const ssrcToUser = new Map(); // audio ssrc -> user id const rtpCount = new Map(); // ssrc -> packet count const voiceState = { session_id: null, token: null, endpoint: null, ssrc: null, ip: null, port: null, mode: null }; +let leaving = false; function leaveAndExit(code = 0) { + if (leaving) return; // idempotent: hard ceiling + ready timer + signals must not double-fire + leaving = true; try { mainWs?.send(JSON.stringify({ op: 4, d: { guild_id: GUILD_ID, channel_id: null, self_mute: true, self_deaf: true } })); } catch {} setTimeout(() => { try { voiceWs?.close(); } catch {} @@ -59,6 +62,11 @@ function leaveAndExit(code = 0) { process.on('SIGINT', () => { log('SIGINT'); leaveAndExit(0); }); process.on('SIGTERM', () => { log('SIGTERM'); leaveAndExit(0); }); +// Hard time-box ceiling, armed at startup regardless of handshake state. Without +// this, a partial join (e.g. DAVE/MLS never completes op29/op30 so announceReady +// never fires) would run the selfbot forever — a guardrail hole for a live test. +if (RUN_MS > 0) setTimeout(() => { log(`RUN_MS=${RUN_MS} hard ceiling elapsed — leaving`); leaveAndExit(0); }, RUN_MS); + // ---------- MAIN GATEWAY ---------- mainWs = new WebSocket('wss://gateway.discord.gg/?v=10&encoding=json'); let mainHb; @@ -190,7 +198,7 @@ function maybeConnectVoice() { announced = true; log(`✅ JOINED & READY. channel=${CHANNEL_ID} dave=${voiceState.daveVer} mlsReady=${mlsReady} privacyCode=${daveSession?.voicePrivacyCode || 'n/a'}`); log(' staying connected, listening for speakers…'); - if (RUN_MS > 0) setTimeout(() => { log(`RUN_MS=${RUN_MS} elapsed`); leaveAndExit(0); }, RUN_MS); + // time-box is owned by the startup hard-ceiling timer (armed regardless of ready state) } }