feat(whatsapp): notify the owner when a non-allowlisted sender messages - #53745
feat(whatsapp): notify the owner when a non-allowlisted sender messages#53745marcelopaniza wants to merge 1 commit into
Conversation
Non-allowlisted WhatsApp senders are silently dropped at the bridge, so the
owner never learns someone tried to reach them — a friend not yet on the list, a
new contact. It is invisible and easily mis-debugged as "the bot ignored me".
Surface it instead, with a one-tap approval path:
- bridge.js emits a deduped `knock` event (sender + a short, sanitized preview)
into the message queue instead of only logging `allowlist_mismatch`, and gains
POST /allow to add a number to the live allowlist at runtime (persisted to
allowlist-runtime.txt next to the session and merged at startup — no restart).
- the adapter consumes `knock`: it sends a notice to WHATSAPP_HOME_CHANNEL
("<name> (+<num>) said: <preview> — reply `allow <num>`") and intercepts the
owner's `allow <number>` reply (home channel only) to call POST /allow + confirm.
Security: the stranger's text is treated as untrusted — only ever shown to the
owner as a quoted preview, never dispatched to the agent. Only the home channel
can approve. Knock notices are a no-op unless WHATSAPP_HOME_CHANNEL is set, so
existing deployments keep silently dropping non-allowlisted senders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Related cluster: companion to your #53742 (per-sender toolset gating), and overlaps the broader WhatsApp authz PR #53623 and the four-tier-concierge RFC #16017. Distinct concern (stranger-knock notify), so not a duplicate — a human should pick how these compose. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing an actual visibility gap: current main silently logs and drops denied non-allowlisted senders at scripts/whatsapp-bridge/bridge.js:637-647.
Problems
plugins/platforms/whatsapp/adapter.py:1203-1204authorizesallowwhen the chat matchesWHATSAPP_HOME_CHANNEL. The home channel is documented as a general notification/cron chat (plugins/platforms/whatsapp/plugin.yaml:26-29), so a group home channel lets any allowed group member approve a new number.scripts/whatsapp-bridge/bridge.js:207-212adds an unboundedknockSeenmap; rejected messages from distinct senders accumulate for the process lifetime.- The diff adds no automated tests for the new authorization and persistent-allowlist paths.
AGENTS.md:84-87requires real-path validation for security boundaries and file/network I/O.
Suggested changes
- Require a verified owner sender for approval, or reject group home channels for this command; do not grant approval from
chatIdequality. - Bound/expire the dedupe cache and add bridge/adapter regressions for authorization, persistence, and dedupe.
- Port this onto current
bridge.js:637-647, preserving the newer pairing-mode and owner-message-gate behavior.
Automated hermes-sweeper review.
| body = (data.get("body") or "").strip() | ||
| if home_chat and len(body) >= 6 and body[:6].lower() == "allow ": | ||
| hc = self._wa_norm(home_chat) | ||
| if hc and (self._wa_norm(data.get("chatId") or "") == hc |
There was a problem hiding this comment.
WHATSAPP_HOME_CHANNEL is a generic notification/cron chat and may be a group. Matching chatId here grants every allowed member of that group authority to add users. Require a verified owner sender identity, or reject group home channels for approval.
| const tsNow = Date.now(); | ||
| const last = knockSeen.get(senderId) || 0; | ||
| if (tsNow - last < KNOCK_DEDUP_MS) return; | ||
| knockSeen.set(senderId, tsNow); |
There was a problem hiding this comment.
knockSeen has no size limit or expiry cleanup, so each distinct rejected sender leaves an entry for the bridge lifetime. Please use a bounded/expiring cache and cover eviction or expiry.
Problem
When a WhatsApp sender isn't on
WHATSAPP_ALLOWED_USERS, the bridge drops themessage and only logs
{"event":"ignored","reason":"allowlist_mismatch"}. Theowner has no idea anyone tried to reach them — a friend who isn't on the list
yet, a new contact for an event RSVP, etc. From the sender's side it just looks
like the bot ignored them.
What this adds
A "stranger knocking" notice with one-tap approval — without weakening the
allowlist (the stranger still can't talk to the agent):
scripts/whatsapp-bridge/bridge.jsallowlist_mismatchbranch, emit a dedupedknockevent (sender idmessage queue instead of only logging. De-dup is one notice per sender per 6h.
POST /allowendpoint: normalizes a number, adds it to the liveALLOWED_USERSset, and appends it toallowlist-runtime.txtnext to thesession (loaded + merged at startup) — so approvals take effect immediately and
survive a restart, with no env edit.
plugins/platforms/whatsapp/adapter.py_poll_messagesroutes each item through_handle_special_inboundfirst:knock→ send a notice toWHATSAPP_HOME_CHANNEL:🔔 <name> (+<num>) said: "<preview>" — replyallow <num>to let them inallow <number>reply (home channel only) →POST /allow+ confirm.Security
quoted preview and is never dispatched to the agent (the knock returns early).
allow <number>from any other sender isignored).
WHATSAPP_HOME_CHANNELis set — deployments without a homechannel keep silently dropping non-allowlisted senders, unchanged.
Testing
Verified on a live bot-mode deployment: a non-allowlisted number triggers exactly
one
🔔notice to the home channel;allow <num>adds them (live, no restart)and they can then converse; repeat messages from the same stranger are de-duped;
POST /allowreturns{"success":true,"id":"..."}and writes the runtime file.🤖 Generated with Claude Code