-
-
Notifications
You must be signed in to change notification settings - Fork 80.7k
Outbound message ownership can split when agentId and sessionKey disagree #96075
Copy link
Copy link
Open
Labels
P2Normal backlog priority with limited blast radius.Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.ClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.Security boundary, credential, authz, sandbox, or sensitive-data risk.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Description
Metadata
Metadata
Assignees
Labels
P2Normal backlog priority with limited blast radius.Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.ClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.Security boundary, credential, authz, sandbox, or sensitive-data risk.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.Session, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Type
Fields
Priority
None yet
Problem
Outbound message paths can accept an explicit
agentIdtogether with an agent-scopedsessionKeyowned by a different agent. When that happens, the same send/action may use one agent for media roots, account policy, or action dispatch, while using another agent's session key for hooks, transcript mirroring, or session bookkeeping.For example, a request shaped like:
agentId: "work"sessionKey: "agent:main:..."can leave outbound delivery treating the active agent as
work, while session-bound behavior still records or correlates againstmain.This is not limited to the Gateway
send/message.actionsurface. Lower outbound helpers can also receive or construct the same inconsistent ownership metadata, including:src/infra/outbound/message-action-runner.tssrc/infra/outbound/message.tssrc/infra/outbound/outbound-send-service.tssrc/infra/outbound/deliver.tsBecause these helpers sit below the Gateway, internal callers can theoretically bypass the Gateway validation and still create split-owner outbound operations.
Why This Matters
OutboundSessionContext.keydocuments the ownership contract that this breaks insrc/infra/outbound/session-context.ts: the canonical session key used for internal hook dispatch must equal the agent runtime'sparams.sessionKey, because plugins observing both agent-loop events and outboundmessage_sending/message_senthooks rely on that equality to correlate per-turn state.A mismatched
agentId+ agent-scopedsessionKeycan produce an outbound context whose explicit agent owner and canonical session owner belong to different agents. That can split owner-scoped behavior from session-scoped behavior inside one send.Expected Behavior
Any outbound API that accepts both an explicit agent owner and an agent-scoped session key should reject the request when the owners disagree.
The check should compare only the agent owner portion of
agent:<owner>:..., not the full session key.These should remain valid:
agentIdrequesterSessionKeyandmirror.sessionKeythat differ but both belong to the same agentpolicyKeyvalues that intentionally describe a different policy/origin session, such as routed ACP deliverypolicyKeymust not be treated as a canonical owner key for equality checks, because it can intentionally represent a separate policy/origin session whilesession.key/mirror.sessionKeyremain the control or transcript owner keys.Affected Behavior
A contradictory pair like:
agentId: "work"sessionKey: "agent:main:slack:channel:c1"should be rejected before:
Proposed Fix
Add a shared owner-mismatch helper based on
parseAgentSessionKey()andnormalizeAgentId().Apply it at the ownership boundaries that can combine explicit agent IDs with session keys:
sendmessage.actionrunMessageAction()sendMessage()executeSendAction()executePollAction()deliverOutboundPayloads()For
deliverOutboundPayloads(), compare the owner fields that actually exist there:session.agentIdagainstsession.key, andmirror.agentIdagainstmirror.sessionKey. Do not comparesession.policyKey.Implementation branch:
bug/outbound-owner-mismatch.Tests
Add regression coverage for:
sendrejects mismatchedagentId+sessionKeymessage.actionrejects mismatchedagentId+sessionKeyrunMessageAction()rejects before plugin dispatchsendMessage()rejects mismatchedagentId+requesterSessionKeysendMessage()rejects mismatchedagentId/mirror.agentId+mirror.sessionKeyexecuteSendAction()andexecutePollAction()reject before plugin/core dispatchdeliverOutboundPayloads()rejects before queueing/sending/mirroringpolicyKeydelivery remains accepted