Skip to content

fix(agent): route explicit channel targets per recipient#73403

Open
vincentkoc wants to merge 1 commit into
mainfrom
clownfish/ghcrawl-157028-autonomous-smoke
Open

fix(agent): route explicit channel targets per recipient#73403
vincentkoc wants to merge 1 commit into
mainfrom
clownfish/ghcrawl-157028-autonomous-smoke

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Review notes

  • Keep explicit --session-key authoritative over --session-id lookup.
  • Preserve global-session continuity for non-deliverable-channel paths.
  • Ensure cross-store session-id scans skip the already-searched primary agent store, not an unrelated default store.

Validation

  • pnpm check:changed

ProjectClownfish replacement details:

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 28, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded session key derivation from user-controlled --channel/--to can bloat sessions.json (DoS)
2 🟡 Medium PII exposure: session keys embed recipient identifiers (e.g., phone numbers) and may be persisted/logged
1. 🟡 Unbounded session key derivation from user-controlled --channel/--to can bloat sessions.json (DoS)
Property Value
Severity Medium
CWE CWE-400
Location src/agents/command/session.ts:235-249

Description

resolveSessionKeyForRequest now derives a session key from CLI-provided --channel and --to via buildAgentPeerSessionKey. These values are user-controlled strings with no length bound.

Impact:

  • buildAgentPeerSessionKey lowercases but does not enforce a maximum length for channel/peerId.
  • The resulting sessionKey is used as a key into the in-memory sessionStore object loaded from disk (sessions.json).
  • Downstream, session updates persist the whole store back to disk; extremely large keys can cause excessive memory usage and large JSON writes, leading to resource exhaustion / denial of service.

Vulnerable code (new behavior):

const explicitChannelSessionKey =
  requestedAgentId && explicitChannel && explicitTo && !requestedSessionId
    ? buildAgentPeerSessionKey({
        channel: explicitChannel,
        peerId: explicitTo,
        dmScope: "per-channel-peer",
      })
    : undefined;

While this does not appear to be used as a filesystem path, the lack of size bounds allows attacker-controlled inputs to create pathological session keys and oversized session stores.

Recommendation

Enforce strict validation and size limits on inputs used to derive session keys.

Suggested approach:

  1. Add a maximum length for channel and to (and/or for the final sessionKey).
  2. Reject or truncate values that exceed the limit.

Example:

function assertBoundedToken(name: string, value: string, max = 128) {
  const trimmed = value.trim();
  if (!trimmed) throw new Error(`${name} must not be empty`);
  if (trimmed.length > max) throw new Error(`${name} too long`);
  return trimmed;
}

const explicitChannel = opts.channel ? assertBoundedToken("channel", opts.channel, 64) : undefined;
const explicitTo = opts.to ? assertBoundedToken("to", opts.to, 128) : undefined;

Optionally, also add an upper bound in buildAgentPeerSessionKey (defense in depth) and/or hash long peer IDs (e.g., sha256(peerId) with a prefix) to keep keys stable and small.

2. 🟡 PII exposure: session keys embed recipient identifiers (e.g., phone numbers) and may be persisted/logged
Property Value
Severity Medium
CWE CWE-532
Location src/agents/command/session.ts:235-245

Description

The session key derivation for explicit --agent --channel --to requests now embeds the raw recipient identifier (often an E.164 phone number) into sessionKey.

  • Input (PII): opts.to / explicitTo may contain a phone number or other recipient identifier.
  • Transformation: buildAgentPeerSessionKey({ dmScope: "per-channel-peer", peerId: explicitTo }) constructs a session key like agent:ops:whatsapp:direct:+15551234567.
  • Exposure: sessionKey is then sent to the Gateway as a request parameter, and is also used as a session-store key. Session store operations may log activeSessionKey (e.g., maintenance warnings) and persist session keys in session-store JSON files. This creates a data/PII leakage risk via logs, local state files, telemetry, crash reports, etc.

Vulnerable code (new behavior):

const explicitChannelSessionKey =
  requestedAgentId && explicitChannel && explicitTo && !requestedSessionId
    ? buildAgentPeerSessionKey({
        agentId: storeAgentId,
        mainKey,
        channel: explicitChannel,
        peerKind: "direct",
        peerId: explicitTo,
        dmScope: "per-channel-peer",
      })
    : undefined;

Recommendation

Avoid embedding raw recipient identifiers (phone numbers, handles, emails) directly in session keys.

Recommended approaches:

  1. Use an opaque, non-PII identifier for per-peer sessions (store a mapping in the session entry):
import crypto from "node:crypto";

function peerSessionSuffix(channel: string, peerId: string) {// Use a keyed HMAC so it cannot be reversed and is stable per deployment.
  const secret = process.env.OPENCLAW_SESSION_KEY_SALT!; // provision securely
  const h = crypto.createHmac("sha256", secret);
  h.update(`${channel}:${peerId}`);
  return h.digest("hex").slice(0, 32);
}

const explicitChannelSessionKey =
  requestedAgentId && explicitChannel && explicitTo && !requestedSessionId
    ? `agent:${storeAgentId}:${explicitChannel}:direct:${peerSessionSuffix(explicitChannel, explicitTo)}`
    : undefined;
  1. Additionally redact session keys in logs/telemetry (e.g., avoid logging activeSessionKey, or log only the non-PII prefix + hash).

These changes preserve per-peer isolation without leaking PII into request parameters, disk state, or logs.


Analyzed PR: #73403 at commit 765765a

Last updated on: 2026-04-28T08:01:09Z

@vincentkoc vincentkoc added clownfish:merge-ready clawsweeper Tracked by ClawSweeper automation labels Apr 28, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation commands Command implementations agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 28, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR fixes openclaw agent --agent ... --channel ... --to ... so it derives a per-recipient session key (agent:<id>:<channel>:direct:<peer>) instead of falling back to the agent main session. It correctly preserves --session-id and --session-key precedence, skips the right primary agent store during cross-store ID scans, and ships focused unit tests that cover all three priority paths.

One minor guard asymmetry: explicitChannelSessionKey is computed (but safely discarded via ??) when requestedSessionKey is already set, whereas shouldPreferExplicitChannelSession already includes !requestedSessionKey as a guard. Aligning the two conditions would eliminate the unnecessary buildAgentPeerSessionKey call and make the precedence invariant self-documenting.

Confidence Score: 4/5

Safe to merge — core session-key derivation logic is correct and well-tested; only a minor code-consistency P2 noted.

No P0 or P1 issues found. The fix is logically sound: --session-key and --session-id both correctly suppress the new channel-derived key path; the ?? operator ensures the right precedence even with the guard asymmetry. Tests cover the main session, the new channel-recipient path, the --session-id override, and the global-scope non-channel path. Score capped at 4 due to the one P2 style finding.

src/agents/command/session.ts — minor guard asymmetry between shouldPreferExplicitChannelSession and explicitChannelSessionKey computation condition.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/command/session.ts
Line: 235-245

Comment:
**Asymmetric guard for `!requestedSessionKey`**

`shouldPreferExplicitChannelSession` (line 214) requires `!requestedSessionKey` to suppress `resolveExplicitAgentSessionKey`, but `explicitChannelSessionKey`'s guard omits that same check. When a caller provides an explicit session key together with agent, channel, and recipient, `buildAgentPeerSessionKey` is called and a channel-derived key is built — only to be silently discarded because `explicitSessionKey` wins via `??`. The behavior is correct, but the extra computation and the divergence between the two guards makes the precedence intent harder to follow. Adding `&& !requestedSessionKey` to the `explicitChannelSessionKey` ternary would align the two guards and avoid the unnecessary call.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(agent): route explicit channel targe..." | Re-trigger Greptile

Comment on lines +235 to +245
const explicitChannelSessionKey =
requestedAgentId && explicitChannel && explicitTo && !requestedSessionId
? buildAgentPeerSessionKey({
agentId: storeAgentId,
mainKey,
channel: explicitChannel,
peerKind: "direct",
peerId: explicitTo,
dmScope: "per-channel-peer",
})
: undefined;
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.

P2 Asymmetric guard for !requestedSessionKey

shouldPreferExplicitChannelSession (line 214) requires !requestedSessionKey to suppress resolveExplicitAgentSessionKey, but explicitChannelSessionKey's guard omits that same check. When a caller provides an explicit session key together with agent, channel, and recipient, buildAgentPeerSessionKey is called and a channel-derived key is built — only to be silently discarded because explicitSessionKey wins via ??. The behavior is correct, but the extra computation and the divergence between the two guards makes the precedence intent harder to follow. Adding && !requestedSessionKey to the explicitChannelSessionKey ternary would align the two guards and avoid the unnecessary call.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/command/session.ts
Line: 235-245

Comment:
**Asymmetric guard for `!requestedSessionKey`**

`shouldPreferExplicitChannelSession` (line 214) requires `!requestedSessionKey` to suppress `resolveExplicitAgentSessionKey`, but `explicitChannelSessionKey`'s guard omits that same check. When a caller provides an explicit session key together with agent, channel, and recipient, `buildAgentPeerSessionKey` is called and a channel-derived key is built — only to be silently discarded because `explicitSessionKey` wins via `??`. The behavior is correct, but the extra computation and the divergence between the two guards makes the precedence intent harder to follow. Adding `&& !requestedSessionKey` to the `explicitChannelSessionKey` ternary would align the two guards and avoid the unnecessary call.

How can I resolve this? If you propose a fix, please make it concise.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 28, 2026

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep this PR open. It is MEMBER-authored and has the protected maintainer label, so cleanup policy requires explicit maintainer handling. Current main also still lacks the proposed explicit --agent --channel --to per-recipient session routing, making the PR a live implementation candidate for the linked open bug rather than obsolete work.

Best possible solution:

Keep this PR open for maintainer review. The likely path is a focused fix that threads normalized channel information through gateway and embedded agent session resolution, derives per-recipient agent session keys only for explicit deliverable channel targets, preserves --session-key and --session-id precedence, skips the correct primary store during session-id scans, and addresses length/redaction concerns around raw recipient-derived session keys before merge.

What I checked:

  • Protected maintainer signal: Provided GitHub context shows authorAssociation MEMBER and the protected maintainer label; repository cleanup policy says these items must stay open for explicit maintainer judgment. (87172dc9fe2c)
  • Docs list checked first: pnpm docs:list completed before inspecting the relevant CLI docs, and it identified docs/cli/agent.md and docs/tools/agent-send.md as relevant to this command surface. (87172dc9fe2c)
  • Current resolver has no channel input: resolveSessionKeyForRequest accepts cfg, to, sessionId, sessionKey, and agentId, but not channel. With an explicit agent and no session id, it resolves the agent main session key before any to-derived key can affect routing. (src/agents/command/session.ts:196, 87172dc9fe2c)
  • Gateway path does not pass channel to session resolution: agentViaGatewayCommand calls resolveSessionKeyForRequest with agentId, to, and sessionId; it normalizes channel only afterward for the gateway delivery params. (src/commands/agent-via-gateway.ts:126, 87172dc9fe2c)
  • Embedded/local path also omits channel: The embedded agent execution path calls resolveSession with to, sessionId, sessionKey, and agentId, but does not thread opts.channel into the session resolver on current main. (src/agents/agent-command.ts:333, 87172dc9fe2c)
  • Docs still describe channel as delivery-only: The current CLI docs say --channel, --reply-channel, and --reply-account affect reply delivery, not session routing, which matches current main rather than this PR's intended behavior. Public docs: docs/cli/agent.md. (docs/cli/agent.md:59, 87172dc9fe2c)

Remaining risk / open question:

  • Closing this PR would violate the cleanup rule for MEMBER-authored and maintainer-labeled items.
  • Closing it would leave the linked open routing bug without an implemented replacement on current main.
  • If maintainers proceed with this PR, they should explicitly resolve the bounds and PII concerns around raw recipient-derived session keys before merge.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 87172dc9fe2c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling clawsweeper Tracked by ClawSweeper automation commands Command implementations docs Improvements or additions to documentation maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: --to flag ignored when using --agent with custom channel, all sessions map to main

1 participant