Skip to content

feat(whatsapp): owner-aware toolset gating for shared lines - #53742

Open
marcelopaniza wants to merge 1 commit into
NousResearch:mainfrom
marcelopaniza:feat/whatsapp-owner-toolset-gating
Open

feat(whatsapp): owner-aware toolset gating for shared lines#53742
marcelopaniza wants to merge 1 commit into
NousResearch:mainfrom
marcelopaniza:feat/whatsapp-owner-toolset-gating

Conversation

@marcelopaniza

Copy link
Copy Markdown

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 line
shared 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_toolsets is
configured (a list of tool names), only owners keep the full toolset; every
other sender has those tools disabled:

whatsapp:
  home_channel:                 # the owner's DM is an owner automatically
    platform: whatsapp
    chat_id: "<owner-id>"
  owner_users:                  # optional additional owners
    - "<id>"
  nonowner_disabled_toolsets:   # presence enables the feature
    - terminal
    - code_execution
    - computer_use
    - file
    - cronjob
    - browser

An owner is the configured whatsapp.home_channel chat_id or any id in
whatsapp.owner_users, matched after normalizing phone / LID / device-suffix
forms (same normalization the allowlist uses).

Implementation

_whatsapp_owner_tool_gate(...) is applied at both _get_platform_tools seams
in gateway/run.py (the main message path and the background-task path), right
where disabled_toolsets is resolved. The gateway already builds a per-session
(per-sender) agent, so the adjusted disabled_toolsets follows the sender with
no caching changes.

Safety

  • Backward compatible: no-op unless platform is WhatsApp and
    nonowner_disabled_toolsets is set — existing deployments are unaffected.
  • Fails closed: once the feature is active, any error restricts (never grants).
  • Owners are unaffected (full toolset).

Tests

tests/gateway/test_whatsapp_owner_tool_gate.py covers opt-in no-op, other
platforms, owner (home channel + owner_users, incl. a device-suffixed LID),
non-owner restriction, preserving pre-existing disabled_toolsets, and
fail-closed on an unknown sender.

🤖 Generated with Claude Code

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>
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/whatsapp WhatsApp Business adapter sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data P2 Medium — degraded but workaround exists labels Jun 27, 2026
@alt-glitch alt-glitch added P3 Low — cosmetic, nice to have and removed P2 Medium — degraded but workaround exists labels Jun 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

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 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:2193 uses source.chat_id to identify the owner. The WhatsApp adapter sets that from data.chatId, while the sender is data.senderId in source.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_toolsets at both AIAgent creation seams.

Automated hermes-sweeper review.

Comment thread gateway/run.py
if owner_id:
owners.add(_norm(owner_id))

sender = getattr(source, "chat_id", None) or getattr(source, "sender_id", None) or ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/whatsapp WhatsApp Business adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants