Skip to content

fix(slack): route system events to bound agent sessions#34045

Merged
vincentkoc merged 11 commits intomainfrom
feat/19809-slack-typing-reaction
Mar 4, 2026
Merged

fix(slack): route system events to bound agent sessions#34045
vincentkoc merged 11 commits intomainfrom
feat/19809-slack-typing-reaction

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Slack inbound system events (notably reaction_added) resolved session keys without channel/account binding-aware agent routing.
  • Why it matters: in multi-account Slack setups with bindings, events could land in agent:main instead of the bound agent session, making reaction notifications appear silently missing.
  • What changed: resolveSlackSystemEventSessionKey now routes via resolveAgentRoute (sender-aware for DMs), and callers now forward senderId for reaction/member/pin/interaction/modal event paths.
  • What did NOT change (scope boundary): event authorization rules, reaction notification mode semantics, and outbound typing/reaction UX behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Slack system events now follow the same binding-aware session routing as normal inbound Slack messages.
  • In multi-account Slack deployments, reaction/member/pin/interaction events are delivered to the correct bound agent session instead of defaulting to agent:main.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local Node/Bun dev env
  • Model/provider: N/A
  • Integration/channel (if any): Slack monitor
  • Relevant config (redacted): Slack multi-account with account bindings

Steps

  1. Configure Slack multi-account with channel/account bindings to non-main agents.
  2. Emit inbound Slack system events (reaction/member/pin/interaction/modal) in bound channels/DMs.
  3. Verify resolved session keys and event enqueue target session.

Expected

  • System events route to the same bound agent session key family as normal inbound messages.

Actual

  • ✅ After patch, events route to binding-aware session keys.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • pnpm vitest run --config vitest.unit.config.ts src/slack/monitor/events/reactions.test.ts src/slack/monitor/events/interactions.test.ts src/slack/monitor/monitor.test.ts
    • pnpm tsgo
    • pnpm check
  • Edge cases checked:
    • channel events resolved through account bindings
    • DM events resolved through sender-aware direct-peer bindings
    • interaction + modal call sites forward sender context
  • What you did not verify:
    • live Slack workspace e2e run in production environment

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • Revert this PR commit set.
  • Files/config to restore:
    • src/slack/monitor/context.ts
    • src/slack/monitor/events/system-event-context.ts
    • src/slack/monitor/events/interactions.ts
    • src/slack/monitor/events/interactions.modal.ts
  • Known bad symptoms reviewers should watch for:
    • Slack system events appearing in the wrong agent session.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Session key shifts for certain system events if sender context is absent in DM paths.
    • Mitigation:
      • Keep legacy fallback key derivation in resolveSlackSystemEventSessionKey when routing context is incomplete.

@vincentkoc vincentkoc marked this pull request as ready for review March 4, 2026 06:16
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes a bug where Slack inbound system events (reactions, member joins/leaves, pins, interactions, modals) were not routed through binding-aware agent session resolution, causing events to land in agent:main instead of the correct bound agent session in multi-account Slack setups.

The fix threads senderId through the call chain so that resolveSlackSystemEventSessionKey can delegate to resolveAgentRoute — the same routing path used for normal inbound messages — before falling back to the existing legacy resolveSessionKey when routing context is incomplete (e.g. unknown sender for a DM).

Key changes:

  • resolveSlackSystemEventSessionKey in context.ts now attempts resolveAgentRoute first, with a robust fallback to legacy key derivation; for DMs, the peer is the sender's user ID; for channels/groups, the peer is the channel ID
  • senderId is forwarded at all call sites: system-event-context.ts (reactions, pins, members), interactions.ts (button clicks), and interactions.modal.ts (view submissions/closures)
  • New tests in monitor.test.ts cover account-binding routing for channel events and direct-peer binding routing for DM events; reactions.test.ts and interactions.test.ts are updated to assert the new senderId field is passed through

Confidence Score: 5/5

  • Safe to merge — the change is backward compatible with a well-tested fallback path for edge cases.
  • The logic is straightforward: try the new binding-aware route, fall back to the legacy path on failure or missing context. The implementation is consistent across all event types, the new code path is guarded by a non-empty peerId check and a catch block, and the author has added targeted unit tests covering binding-match and fallback scenarios. No authorization semantics were changed, no new network calls or permissions introduced, and all existing tests remain valid.
  • No files require special attention.

Last reviewed commit: d8a7d9c

@vincentkoc vincentkoc merged commit c1bb07b into main Mar 4, 2026
9 checks passed
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 4, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟡 Medium Slack DM system events can be misrouted by using message author/bot_id as senderId for session routing

1. 🟡 Slack DM system events can be misrouted by using message author/bot_id as senderId for session routing

Property Value
Severity Medium
CWE CWE-284
Location src/slack/monitor/context.ts:173-187

Description

resolveSlackSystemEventSessionKey now routes DM ("im") system events via resolveAgentRoute() using senderId as the DM peer identifier.

For several system-event sources, senderId is not guaranteed to be the DM peer user:

  • Message subtype system events (e.g., message_changed, message_deleted) derive senderId from the message author (message.user/previous_message.user) and fall back to bot_id.
  • In a DM, when the bot updates its own message, Slack commonly emits message_changed where message.user is absent and message.bot_id is present.

As a result, DM system events can be routed to a session bound to the bot ID (or to an account/default binding) instead of the intended per-user DM session. In deployments using per-peer bindings to isolate agents (e.g., VIP/customer-specific agents), this can cause cross-session information disclosure (DM activity metadata appearing in the wrong agent/session) and session confusion.

Vulnerable routing logic:

const peerId = isDirectMessage ? senderId : channelId;
...
return route.sessionKey;

Example of non-peer senderId source (bot message edit/delete):

return changed.message?.bot_id ?? changed.previous_message?.bot_id;

Recommendation

For DMs, resolve the actual DM peer user ID from the channelId (via conversations.info) and use that as the routing peer, instead of senderId.

Suggested approach:

  1. Extend resolveChannelName() (or add a new resolver) to also return the DM peer user ID when channel.is_im:
// when conversations.info() returns an IM
const dmUser = (channel as any).user as string | undefined;
  1. In resolveSlackSystemEventSessionKey, prefer that DM peer id:
const channelType = normalizeSlackChannelType(p.channelType, channelId);
if (channelType === "im") {
  const info = await resolveChannelInfo(channelId); // includes dmUser
  const dmPeerId = info.userId;
  const peerId = dmPeerId ?? undefined; // fallback handled below
  if (peerId) {
    return resolveAgentRoute({
      cfg: params.cfg,
      channel: "slack",
      accountId: params.accountId,
      teamId: params.teamId,
      peer: { kind: "direct", id: peerId },
    }).sessionKey;
  }
}
  1. As a defense-in-depth fallback, if you keep senderId routing for DMs, only use it when it looks like a Slack user ID (e.g., /^[UW]/) and otherwise fall back to the legacy per-channel key to avoid collapsing multiple users into a bot-id-derived route.

Analyzed PR: #34045 at commit ee664d3

Last updated on: 2026-03-04T14:09:48Z

@vincentkoc vincentkoc deleted the feat/19809-slack-typing-reaction branch March 4, 2026 13:44
mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 4, 2026
* main:
  Compaction/Safeguard: require structured summary headings (openclaw#25555)
  Fix gateway restart false timeouts on Debian/systemd (openclaw#34874)
  fix(review): enforce behavioral sweep validation
  chore(changelog): clarify outbound media-only fallback openclaw#32788 thanks @liuxiaopai-ai
  fix(outbound): fail media-only text-only adapter fallback
  chore(changelog): align outbound adapter entry openclaw#32788 thanks @liuxiaopai-ai
  Outbound: avoid empty multi-media fallback sends
  Outbound: allow text-only plugin adapters
  chore(changelog): add PR entry openclaw#24337 thanks @echoVic
  test(ollama): add default header precedence coverage
  fix(ollama): pass provider headers to Ollama stream function (openclaw#24285)
  Agents: add generic poll-vote action support
  fix(node-host): sync rawCommand with hardened argv after executable path pinning (openclaw#33137)
  fix(daemon): handle systemctl is-enabled exit 4 (not-found) on Ubuntu (openclaw#33634)
  fix(model): propagate custom provider headers to model objects (openclaw#27490)
  fix(memory): serialize local embedding initialization to avoid duplicate model loads (openclaw#15639)
  Delete changelog/fragments directory
  fix(slack): route system events to bound agent sessions (openclaw#34045)