WhatsApp allowlist and self-chat authorization controls
NousResearch/hermes-agent · cluster 8389 · gateway
Data as of
Nothing open on GitHub. Fix merged in #21291. Check it covers your case.
The short answer
- Known?
- Yes. 7 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #21291. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #21291. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and pull requests address authorization behavior in WhatsApp integration, focusing on how an empty or missing WHATSAPP_ALLOWED_USERS allowlist is handled and how self-chat mode responds to incoming messages from unknown senders.
Open issues0
Open PRs0
Fixes on record
-
#21291 fix(whatsapp): reject strangers by default, never respond in self-chat (#8389)
files:
scripts/whatsapp-bridge/allowlist.js,scripts/whatsapp-bridge/allowlist.test.mjs,scripts/whatsapp-bridge/bridge.jsShow the diff
-
scripts/whatsapp-bridge/allowlist.js@@ -64,8 +64,12 @@ export function expandWhatsAppIdentifiers(identifier, sessionDir) { } export function matchesAllowedUser(senderId, allowedUsers, sessionDir) { + // Empty allowlist = NO ONE allowed (secure default, #8389). Operators + // who want an open bot must set ``WHATSAPP_ALLOWED_USERS=*`` explicitly. + // Previous behaviour (empty → return true) let any stranger DM the + // bridge and trigger a Python-side pairing-code reply. if (!allowedUsers || allowedUsers.size === 0) { - return true; + return false; } // "*" means allow everyone (consistent with SIGNAL_GROUP_ALLOWED_USERS) -
scripts/whatsapp-bridge/allowlist.test.mjs@@ -57,3 +57,24 @@ test('matchesAllowedUser treats * as allow-all wildcard', () => { rmSync(sessionDir, { recursive: true, force: true }); } }); + +test('matchesAllowedUser rejects everyone when allowlist is empty (#8389)', () => { + // Regression guard: empty allowlist used to return true (allow-everyone), + // which let any stranger DM the bridge and trigger a Python-side + // pairing-code reply. Secure default is now "reject unless explicitly + // configured"; operators who want an open bot must set `*`. + const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-')); + + try { + const empty = parseAllowedUsers(''); + assert.equal(empty.size, 0); + assert.equal(matchesAllowedUser('[redacted:email]', empty, sessionDir), false); + assert.equal(matchesAllowedUser('267383306489914@lid', empty, sessionDir), false); + + // Null/undefined allowlist (defensive) also rejects. + assert.equal(matchesAllowedUser('[redacted:email]', null, sessionDir), false); + assert.equal(matchesAllowedUser('[redacted:email]', undefined, sessionDir), false); + } finally { + rmSync(sessionDir, { recursive: true, force: true }); + } +}); -
scripts/whatsapp-bridge/bridge.js@@ -229,17 +229,34 @@ async function startSocket() { if (!isSelfChat) continue; } - // Check allowlist for messages from others (resolve LID ↔ phone aliases) - if (!msg.key.fromMe && !matchesAllowedUser(senderId, ALLOWED_USERS, SESSION_DIR)) { - try { - console.log(JSON.stringify({ - event: 'ignored', - reason: 'allowlist_mismatch', - chatId, - senderId, - })); - } catch {} - continue; + // Handle !fromMe messages (from other people) based on mode. + // Self-chat mode only responds to the user's own messages to + // themselves — stranger DMs / group pings must never reach the + // Python gateway, otherwise a pairing-code reply fires in response + // to arbitrary incoming messages (#8389). + if (!msg.key.fromMe) { + if (WHATSAPP_MODE === 'self-chat') { + try { + console.log(JSON.stringify({ + event: 'ignored', + reason: 'self_chat_mode_rejects_non_self', + chatId, + senderId, + })); + } catch {} + continue; + } + if (!matchesAllowedUser(senderId, ALLOWED_USERS, SESSION_DIR)) { + try { + console.log(JSON.stringify({ + event: 'ignored', + reason: 'allowlist_mismatch', + chatId, + senderId, + })); + } catch {} + continue; + } } const messageContent = getMessageContent(msg); @@ -625,8 +642,12 @@ if (PAIR_ONLY) { console.log(`📁 Session stored in: ${SESSION_DIR}`); if (ALLOWED_USERS.size > 0) { console.log(`🔒 Allowed users: ${Array.from(ALLOWED_USERS).join(', ')}`); + } else if (WHATSAPP_MODE === 'self-chat') { + console.log(`🔒 Self-chat mode — only your own messages to yourself are processed.`); } else { - console.log(`⚠️ No WHATSAPP_ALLOWED_USERS set — all messages will be processed`); + console.log(`🔒 No WHATSAPP_ALLOWED_USERS set — incoming messages are rejected.`); + console.log(` Set WHATSAPP_ALLOWED_USERS=<phone> to authorize specific users,`); + console.log(` or WHATSAPP_ALLOWED_USERS=* for an explicit open bot.`); } console.log(); startSocket();
-
4 PRs closed without merging
From the close comments: done elsewhere 2 · superseded by another PR 2
-
#8410 security(whatsapp): deny unknown senders when WHATSAPP_ALLOWED_USERS is empty (#8389) · superseded by another PR →
#21291,#8389,#8431 -
#8431 fix(whatsapp/bridge): #8389 patch critical authorization bypass in self-chat mode · done elsewhere →
#8389 -
#21009 fix(whatsapp): deny empty bridge allowlist · done elsewhere →
413990c -
#30288 fix(whatsapp): allow group policy gating in self-chat mode · superseded by another PR →
#20429
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.26: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- gateway
- Merged PRs
- #21291
- Closed issues
- 2
- Attempts
- 4 PRs closed without merging
- Fix link checked
- 7 May 2026