add sender allowlist for per-chat access control (#705)

* feat: add sender allowlist for per-chat access control

* style: fix prettier formatting
This commit is contained in:
Akshan Krithick
2026-03-04 08:05:45 -08:00
committed by GitHub
parent a5845a5cf4
commit 4de981b9b9
6 changed files with 423 additions and 7 deletions

View File

@@ -41,6 +41,12 @@ import { GroupQueue } from './group-queue.js';
import { resolveGroupFolderPath } from './group-folder.js';
import { startIpcWatcher } from './ipc.js';
import { findChannel, formatMessages, formatOutbound } from './router.js';
import {
isSenderAllowed,
isTriggerAllowed,
loadSenderAllowlist,
shouldDropMessage,
} from './sender-allowlist.js';
import { startSchedulerLoop } from './task-scheduler.js';
import { Channel, NewMessage, RegisteredGroup } from './types.js';
import { logger } from './logger.js';
@@ -155,8 +161,11 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
// For non-main groups, check if trigger is required and present
if (!isMainGroup && group.requiresTrigger !== false) {
const hasTrigger = missedMessages.some((m) =>
TRIGGER_PATTERN.test(m.content.trim()),
const allowlistCfg = loadSenderAllowlist();
const hasTrigger = missedMessages.some(
(m) =>
TRIGGER_PATTERN.test(m.content.trim()) &&
(m.is_from_me || isTriggerAllowed(chatJid, m.sender, allowlistCfg)),
);
if (!hasTrigger) return true;
}
@@ -380,8 +389,12 @@ async function startMessageLoop(): Promise<void> {
// Non-trigger messages accumulate in DB and get pulled as
// context when a trigger eventually arrives.
if (needsTrigger) {
const hasTrigger = groupMessages.some((m) =>
TRIGGER_PATTERN.test(m.content.trim()),
const allowlistCfg = loadSenderAllowlist();
const hasTrigger = groupMessages.some(
(m) =>
TRIGGER_PATTERN.test(m.content.trim()) &&
(m.is_from_me ||
isTriggerAllowed(chatJid, m.sender, allowlistCfg)),
);
if (!hasTrigger) continue;
}
@@ -465,7 +478,29 @@ async function main(): Promise<void> {
// Channel callbacks (shared by all channels)
const channelOpts = {
onMessage: (_chatJid: string, msg: NewMessage) => storeMessage(msg),
onMessage: (_chatJid: string, msg: NewMessage) => {
// Sender allowlist drop mode: discard messages from denied senders before storing
if (
!msg.is_from_me &&
!msg.is_bot_message &&
registeredGroups[_chatJid]
) {
const cfg = loadSenderAllowlist();
if (
shouldDropMessage(_chatJid, cfg) &&
!isSenderAllowed(_chatJid, msg.sender, cfg)
) {
if (cfg.logDenied) {
logger.debug(
{ chatJid: _chatJid, sender: msg.sender },
'sender-allowlist: dropping message (drop mode)',
);
}
return;
}
}
storeMessage(msg);
},
onChatMetadata: (
chatJid: string,
timestamp: string,