feat(whatsapp): owner-aware toolset gating for shared lines - #53742
feat(whatsapp): owner-aware toolset gating for shared lines#53742marcelopaniza wants to merge 1 commit into
Conversation
A shared / community WhatsApp number exposes the full agent toolset (terminal, code_execution, computer_use, file, browser, ...) to every allowlisted sender. On a line used by family/friends, a single compromised or socially-engineered contact can drive the agent into shell access or exfiltration. The allowlist controls who can talk to the agent; it does not control what each of them can make it do. Add opt-in per-sender gating: when whatsapp.nonowner_disabled_toolsets is set, only owners (the whatsapp.home_channel chat_id or ids in whatsapp.owner_users) keep the full toolset; every other sender has those tools disabled. Applied at both _get_platform_tools seams in gateway/run.py (main + background-task paths); the gateway already builds a per-session/per-sender agent, so the resolved toolset follows the sender. Backward compatible: no config -> no behavior change. Fails closed: once active, any error restricts rather than grants. Owner identifiers are normalized for phone/LID/device-suffix forms, consistent with the existing allowlist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Related cluster: companion to your #53745 (stranger-knock notify) addressing a different dimension (what each sender can make the agent DO vs who can talk to it), and overlaps the broader WhatsApp authz PR #53623 and RFC #16017. Distinct concern, not a duplicate. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real gap: current main applies only global agent.disabled_toolsets in both agent-construction paths (gateway/run.py:13379-13381, gateway/run.py:17045-17047).
Problems
- Blocking:
gateway/run.py:2193usessource.chat_idto identify the owner. The WhatsApp adapter sets that fromdata.chatId, while the sender isdata.senderIdinsource.user_id(plugins/platforms/whatsapp/adapter.py:1360-1365). A configured home group therefore makes every group member match the home-channel ID and retain the full toolset. - The local normalization does not implement the existing phone↔LID alias resolution used by WhatsApp authorization (
gateway/authz_mixin.py:577-588;gateway/whatsapp_identity.py:121-170). The tests do not cover a mapped, numerically distinct LID and phone.
Suggested changes
- Match
source.user_id_alt or source.user_id, using the shared WhatsApp identity helpers; do not derive owner status from the conversation ID. - Add group-home-channel and mapped phone/LID regressions, and verify the effective
disabled_toolsetsat both AIAgent creation seams.
Automated hermes-sweeper review.
| if owner_id: | ||
| owners.add(_norm(owner_id)) | ||
|
|
||
| sender = getattr(source, "chat_id", None) or getattr(source, "sender_id", None) or "" |
There was a problem hiding this comment.
chat_id is the conversation ID, not the sender identity. The WhatsApp adapter builds it from data.chatId and puts data.senderId in source.user_id (plugins/platforms/whatsapp/adapter.py:1360-1365); a home channel that is a group would therefore grant every group member the owner toolset. Match user_id_alt/user_id with the shared WhatsApp alias-resolution helper instead.
Motivation
A WhatsApp line configured as a shared / community bot answers many people, but
the agent's WhatsApp toolset includes powerful tools —
terminal,code_execution,computer_use,file,browser,cronjob, … . On a lineshared with family/friends, every allowlisted sender can (deliberately, or via a
compromised/duped number) prompt-inject the agent into shell access or
exfiltration. The allowlist controls who can talk to the agent; it does not
control what each of them can make it do.
What this adds
Opt-in, per-sender toolset gating. When
whatsapp.nonowner_disabled_toolsetsisconfigured (a list of tool names), only owners keep the full toolset; every
other sender has those tools disabled:
An owner is the configured
whatsapp.home_channelchat_id or any id inwhatsapp.owner_users, matched after normalizing phone / LID / device-suffixforms (same normalization the allowlist uses).
Implementation
_whatsapp_owner_tool_gate(...)is applied at both_get_platform_toolsseamsin
gateway/run.py(the main message path and the background-task path), rightwhere
disabled_toolsetsis resolved. The gateway already builds a per-session(per-sender) agent, so the adjusted
disabled_toolsetsfollows the sender withno caching changes.
Safety
nonowner_disabled_toolsetsis set — existing deployments are unaffected.Tests
tests/gateway/test_whatsapp_owner_tool_gate.pycovers opt-in no-op, otherplatforms, owner (home channel +
owner_users, incl. a device-suffixed LID),non-owner restriction, preserving pre-existing
disabled_toolsets, andfail-closed on an unknown sender.
🤖 Generated with Claude Code