Summary
channel_send's account_id parameter is accepted from the model with zero validation — an agent can dispatch a message to any configured channel account (i.e. any other bot/tenant instance registered on the daemon), not just the one it was invoked from. In a multi-tenant setup (distinct bot accounts serving different customers/organizations on the same daemon), this is a cross-tenant confidentiality boundary, not just a UX footgun.
This is the same bug class as #6117 (cross-chat dispatch via a mismatched recipient on the same channel) but a wider blast radius: #6117's fix explicitly scopes itself to same-channel re-targeting and states cross-channel dispatch "stays allowed" by design. account_id (cross-account, i.e. effectively cross-tenant) sits entirely outside that guard.
Verified against the current stable release v2026.7.11 (pinned to the git tag, not main).
Root cause
crates/librefang-runtime/src/tool_runner/channel.rs, inside tool_channel_send():
let thread_id = trim_opt_string(input["thread_id"].as_str());
let account_id = trim_opt_string(input["account_id"].as_str());
account_id is extracted from the tool-call input and passed straight through to kh.send_channel_media(...) / the text-send equivalent with no allowlist check against the calling agent, no comparison to the turn's originating channel/account, and no capability gate beyond the coarse channel.* capability that merely grants/denies the tool as a whole.
The #6117 cross-chat guard (same file, a few lines above) only fires when:
if let (Some(explicit), Some(turn_channel)) = (explicit_recipient, sender_channel) {
...
if turn_channel.eq_ignore_ascii_case(&channel) && explicit != expected { ... }
}
— i.e. it compares channel (the channel identity the request is being sent on, e.g. a specific bot instance) against the turn's inbound channel, and only rejects a mismatched recipient within the same channel. A channel_send call that targets an entirely different channel/account_id bypasses this check completely — which the issue that introduced it (#6117) explicitly calls out as intentional scope-narrowing ("a different-channel dispatch... stays allowed").
Related but orthogonal existing mechanism: ChannelToolRule (crates/librefang-types/src/approval.rs) gates which tools may run for requests originating from a channel (inbound). It has no notion of restricting which channel/account an agent's outbound channel_send may target.
Why this matters (multi-tenant impact)
A common deployment shape is one daemon running several bot instances, each serving a different customer/organization (e.g. bot-A for customer A, bot-B for customer B), each bound to its own agent via default_agent. If the agent behind bot-A can be induced — via model hallucination or prompt injection in an inbound message it processes (the same trigger class documented as the root cause in #6117's live incident) — to call channel_send(account_id="bot-B", ...), it will silently dispatch attacker- or model-chosen content into a different customer's chat. No approval, no warning, no audit distinction from a same-account send.
Proposed fix
Minimal: extend the existing #6117-style guard to also validate account_id (when present) against the turn's originating account, using the same "explicit-value-only" scoping (auto-filled sends already target the correct account).
More complete: an opt-in per-agent capability, e.g.:
[capabilities.channel_send]
allowed_accounts = ["bot-A"]
allowed_peers = ["<peer-id>"]
enforced the same way ChannelToolRule enforces inbound tool gating today, so operators running single-tenant setups aren't forced to enumerate every legitimate escalation path.
Workaround in use today: replace the native channel_send in the agent's capabilities.tools with a custom MCP tool whose schema hardcodes account_id/peer_id as constants (the agent physically cannot pass a different value — the parameter isn't in the schema).
Setup (for reference)
Happy to test a patch.
Summary
channel_send'saccount_idparameter is accepted from the model with zero validation — an agent can dispatch a message to any configured channel account (i.e. any other bot/tenant instance registered on the daemon), not just the one it was invoked from. In a multi-tenant setup (distinct bot accounts serving different customers/organizations on the same daemon), this is a cross-tenant confidentiality boundary, not just a UX footgun.This is the same bug class as #6117 (cross-chat dispatch via a mismatched
recipienton the same channel) but a wider blast radius: #6117's fix explicitly scopes itself to same-channel re-targeting and states cross-channel dispatch "stays allowed" by design.account_id(cross-account, i.e. effectively cross-tenant) sits entirely outside that guard.Verified against the current stable release v2026.7.11 (pinned to the git tag, not
main).Root cause
crates/librefang-runtime/src/tool_runner/channel.rs, insidetool_channel_send():account_idis extracted from the tool-call input and passed straight through tokh.send_channel_media(...)/ the text-send equivalent with no allowlist check against the calling agent, no comparison to the turn's originating channel/account, and no capability gate beyond the coarsechannel.*capability that merely grants/denies the tool as a whole.The #6117 cross-chat guard (same file, a few lines above) only fires when:
— i.e. it compares
channel(the channel identity the request is being sent on, e.g. a specific bot instance) against the turn's inbound channel, and only rejects a mismatched recipient within the same channel. Achannel_sendcall that targets an entirely differentchannel/account_idbypasses this check completely — which the issue that introduced it (#6117) explicitly calls out as intentional scope-narrowing ("a different-channel dispatch... stays allowed").Related but orthogonal existing mechanism:
ChannelToolRule(crates/librefang-types/src/approval.rs) gates which tools may run for requests originating from a channel (inbound). It has no notion of restricting which channel/account an agent's outboundchannel_sendmay target.Why this matters (multi-tenant impact)
A common deployment shape is one daemon running several bot instances, each serving a different customer/organization (e.g.
bot-Afor customer A,bot-Bfor customer B), each bound to its own agent viadefault_agent. If the agent behindbot-Acan be induced — via model hallucination or prompt injection in an inbound message it processes (the same trigger class documented as the root cause in #6117's live incident) — to callchannel_send(account_id="bot-B", ...), it will silently dispatch attacker- or model-chosen content into a different customer's chat. No approval, no warning, no audit distinction from a same-account send.Proposed fix
Minimal: extend the existing #6117-style guard to also validate
account_id(when present) against the turn's originating account, using the same "explicit-value-only" scoping (auto-filled sends already target the correct account).More complete: an opt-in per-agent capability, e.g.:
enforced the same way
ChannelToolRuleenforces inbound tool gating today, so operators running single-tenant setups aren't forced to enumerate every legitimate escalation path.Workaround in use today: replace the native
channel_sendin the agent'scapabilities.toolswith a custom MCP tool whose schema hardcodesaccount_id/peer_idas constants (the agent physically cannot pass a different value — the parameter isn't in the schema).Setup (for reference)
v2026.7.11(stable), verified against git tagv2026.7.11.default_agent, serving distinct customers.Happy to test a patch.