← Back to PRs

#20806: fix(security): enforce per-agent message send scope in multi-tenant deployments

by zerone0x open 2026-02-19 09:48 View on GitHub →
app: web-ui gateway agents size: M experienced-contributor
## Summary Fixes #20305 Adds a `tools.message.scope` config option (`"unrestricted"` | `"own-session"`) that restricts agents to sending messages only to the peer bound to their session key. When scope is `"own-session"`: - **Cross-user sends are blocked** — `enforceMessageSendScope()` runs *before* target resolution, comparing the raw target against the peer ID embedded in the agent's session key - **Broadcasts are blocked** — since broadcasts fan out to all users, they bypass per-peer scoping and are rejected upfront - **Cron job list is scoped** — `cron.list` now filters by the calling agent's `agentId`, preventing metadata enumeration across tenants ## Changes - **`src/infra/outbound/message-action-runner.ts`** — core enforcement: `extractBoundPeerFromSessionRest()` and `enforceMessageSendScope()` functions; broadcast block in `handleBroadcastAction` - **`src/config/types.tools.ts`** — new `scope` field on message tool config - **`src/config/zod-schema.agent-runtime.ts`** — zod validation for `scope` - **`src/config/schema.labels.ts`** / **`schema.help.ts`** — config labels and help text - **`src/cron/service.ts`** / **`src/cron/service/ops.ts`** — `agentId` filter in `list()` - **`src/gateway/protocol/schema/cron.ts`** — `agentId` param in `CronListParamsSchema` - **`src/gateway/server-methods/cron.ts`** — pass `agentId` through - **`src/agents/tools/cron-tool.ts`** — resolve and pass `agentId` in list action - **`src/infra/outbound/message-action-runner.test.ts`** — 8 new tests covering block/allow paths ## Test plan - [x] 8 new scope enforcement tests (block cross-user, allow own-peer, unrestricted mode, default mode, main dmScope bypass, broadcast block, per-peer keys, per-account-channel-peer keys) - [x] All 36 tests in `message-action-runner.test.ts` pass - [x] Full suite: 7753 passed, 0 regressions (25 pre-existing flaky browser/socket tests) - [x] TypeScript type-check passes (0 new errors) - [x] oxlint passes --- 🤖 Generated with Claude Code (issue-hunter-pro) <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR implements a new `tools.message.scope` configuration to restrict agents in multi-tenant deployments from sending messages across user boundaries. When set to `"own-session"`, agents can only send messages to the peer bound to their session key. **Key Changes:** - Added `tools.message.scope` config option with `"unrestricted"` (default) and `"own-session"` modes - Core enforcement in `message-action-runner.ts` validates targets before resolution - Blocks broadcast operations when scope is `"own-session"` - Scopes `cron.list` results by `agentId` to prevent metadata enumeration - Comprehensive test coverage across different session key formats **Implementation Quality:** - Enforcement runs before target resolution, preventing enumeration attacks - Handles multiple target parameter formats (`target`, `to`, `channelId`) - Gracefully handles edge cases (no session key, legacy formats, main dmScope) - Clean separation of concerns with dedicated enforcement function <h3>Confidence Score: 4/5</h3> - This PR is safe to merge with one minor edge case to consider - Strong security implementation with comprehensive test coverage. The enforcement logic correctly handles multiple parameter formats and edge cases. One theoretical edge case exists with prefix stripping, but it's unlikely to cause issues in practice due to platform ID namespacing. The cron scoping and broadcast blocking are well-implemented. Core functionality is sound and defensive. - No files require special attention beyond the minor style suggestion on the prefix stripping logic <sub>Last reviewed commit: d18f10f</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs