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 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-08-15 19:28:14 +09:00
parent 12f7399b36
commit cfad568029

View File

@@ -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)
}
}