mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
Self-chat mode (default) previously replied to ANY incoming DM with a
Python-side pairing-code message. Two compounding defaults:
1. allowlist.js::matchesAllowedUser returned true for an empty
allowlist — so WHATSAPP_ALLOWED_USERS unset → everyone passes the JS
bridge gate → messages reach Python gateway → _is_user_authorized
returns False but _get_unauthorized_dm_behavior falls back to
'pair' → stranger gets a pairing code reply.
2. bridge.js had no mode check on !fromMe messages, so self-chat mode
(where the operator only wants to talk to themselves) forwarded
everything anyway.
Fix:
- allowlist.js: empty allowlist now returns false. Operators who want
an open bot must set WHATSAPP_ALLOWED_USERS=* explicitly (the
existing wildcard behaviour, consistent with SIGNAL_GROUP_ALLOWED_USERS).
- bridge.js: self-chat mode hard-rejects all !fromMe messages at the
bridge, before they ever reach the Python gateway. Bot mode still
enforces the allowlist.
- Startup log message updated to reflect the new per-mode behaviour
(was '⚠️ No WHATSAPP_ALLOWED_USERS set — all messages will be
processed', which was both inaccurate post-fix and a bad default
signal pre-fix).
- allowlist.test.mjs: new regression test pinning the empty-rejects
contract, + null/undefined defensive cases.
Behaviour delta for existing users:
- self-chat mode, no allowlist: strangers got pairing codes, now
silently dropped. Strictly better.
- bot mode, no allowlist: strangers got pairing codes via the
Python-side pairing flow, now silently dropped at the JS bridge.
Operators who genuinely want an open bot set
WHATSAPP_ALLOWED_USERS=*.
88 lines
2.3 KiB
JavaScript
88 lines
2.3 KiB
JavaScript
import path from 'path';
|
|
import { existsSync, readFileSync } from 'fs';
|
|
|
|
export function normalizeWhatsAppIdentifier(value) {
|
|
return String(value || '')
|
|
.trim()
|
|
.replace(/:.*@/, '@')
|
|
.replace(/@.*/, '')
|
|
.replace(/^\+/, '');
|
|
}
|
|
|
|
export function parseAllowedUsers(rawValue) {
|
|
return new Set(
|
|
String(rawValue || '')
|
|
.split(',')
|
|
.map((value) => normalizeWhatsAppIdentifier(value))
|
|
.filter(Boolean)
|
|
);
|
|
}
|
|
|
|
function readMappingFile(sessionDir, identifier, suffix = '') {
|
|
const filePath = path.join(sessionDir, `lid-mapping-${identifier}${suffix}.json`);
|
|
if (!existsSync(filePath)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
const normalized = normalizeWhatsAppIdentifier(parsed);
|
|
return normalized || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function expandWhatsAppIdentifiers(identifier, sessionDir) {
|
|
const normalized = normalizeWhatsAppIdentifier(identifier);
|
|
if (!normalized) {
|
|
return new Set();
|
|
}
|
|
|
|
// Walk both phone->LID and LID->phone mapping files so allowlists can use
|
|
// either form transparently in bot mode.
|
|
const resolved = new Set();
|
|
const queue = [normalized];
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
if (!current || resolved.has(current)) {
|
|
continue;
|
|
}
|
|
|
|
resolved.add(current);
|
|
|
|
for (const suffix of ['', '_reverse']) {
|
|
const mapped = readMappingFile(sessionDir, current, suffix);
|
|
if (mapped && !resolved.has(mapped)) {
|
|
queue.push(mapped);
|
|
}
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
// "*" means allow everyone (consistent with SIGNAL_GROUP_ALLOWED_USERS)
|
|
if (allowedUsers.has('*')) {
|
|
return true;
|
|
}
|
|
|
|
const aliases = expandWhatsAppIdentifiers(senderId, sessionDir);
|
|
for (const alias of aliases) {
|
|
if (allowedUsers.has(alias)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|