fix(whatsapp): authorize first-contact LID senders

This commit is contained in:
Amit C
2026-07-13 02:38:27 +05:30
committed by kshitij
parent 2bfb95b396
commit 8a55373dbf
3 changed files with 37 additions and 4 deletions

View File

@@ -63,6 +63,15 @@ export function expandWhatsAppIdentifiers(identifier, sessionDir) {
return resolved;
}
export function matchesAllowedSender(senderId, senderPn, allowedUsers, sessionDir) {
// WhatsApp Multi-Device can expose a first-contact sender as an opaque LID
// before it persists LID mapping files. Baileys supplies the same sender's
// authenticated phone JID separately in msg.key.senderPn, so consult it
// as an additional alias without changing the allowlist itself.
return matchesAllowedUser(senderId, allowedUsers, sessionDir)
|| matchesAllowedUser(senderPn, allowedUsers, 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.

View File

@@ -6,6 +6,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import {
expandWhatsAppIdentifiers,
matchesAllowedSender,
matchesAllowedUser,
normalizeWhatsAppIdentifier,
parseAllowedUsers,
@@ -46,6 +47,27 @@ test('matchesAllowedUser accepts mapped lid sender when allowlist only contains
}
});
test('matchesAllowedSender accepts Baileys senderPn when a first-contact LID has no mapping yet', () => {
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));
try {
const allowedUsers = parseAllowedUsers('+19175395595');
// On a first message from a WhatsApp Multi-Device contact, Baileys can
// emit a LID as senderId before it has written LID mapping files, while
// senderPn carries WhatsApp's authenticated phone JID.
assert.equal(
matchesAllowedSender('267383306489914@lid', '19175395595@s.whatsapp.net', allowedUsers, sessionDir),
true,
);
assert.equal(
matchesAllowedSender('267383306489914@lid', '18881234567@s.whatsapp.net', allowedUsers, sessionDir),
false,
);
} finally {
rmSync(sessionDir, { recursive: true, force: true });
}
});
test('matchesAllowedUser treats * as allow-all wildcard', () => {
const sessionDir = mkdtempSync(path.join(os.tmpdir(), 'hermes-wa-allowlist-'));

View File

@@ -30,7 +30,7 @@ import { randomBytes, createHash } from 'crypto';
import { execFileSync } from 'child_process';
import { tmpdir } from 'os';
import qrcode from 'qrcode-terminal';
import { matchesAllowedUser, parseAllowedUsers } from './allowlist.js';
import { matchesAllowedSender, matchesAllowedUser, parseAllowedUsers } from './allowlist.js';
import { createOutboundIdTracker } from './outbound_ids.js';
import { classifyOwnerMessageGate } from './owner_message_gate.js';
import {
@@ -525,8 +525,10 @@ async function startSocket() {
const chatId = msg.key.remoteJid;
const senderId = msg.key.participant || chatId;
const senderPn = msg.key.senderPn || '';
const resolvedSenderId = senderPn || senderId;
const isGroup = chatId.endsWith('@g.us');
const senderNumber = senderId.replace(/@.*/, '');
const senderNumber = resolvedSenderId.replace(/@.*/, '');
emitDebugEvent({
stage: 'upsert',
type,
@@ -627,7 +629,7 @@ async function startSocket() {
} catch {}
continue;
}
if (WHATSAPP_DM_POLICY !== 'pairing' && !matchesAllowedUser(senderId, ALLOWED_USERS, SESSION_DIR)) {
if (WHATSAPP_DM_POLICY !== 'pairing' && !matchesAllowedSender(senderId, senderPn, ALLOWED_USERS, SESSION_DIR)) {
try {
console.log(JSON.stringify({
event: 'ignored',
@@ -703,7 +705,7 @@ async function startSocket() {
const event = await extractBridgeEvent({
msg,
chatId,
senderId,
senderId: resolvedSenderId,
senderNumber,
botIds,
isGroup,