Skip to content

security(slack): isolate room text-command auth from DM pairing-store entries#26098

Closed
bmendonca3 wants to merge 6 commits intoopenclaw:mainfrom
bmendonca3:bm/security-7th-20260225
Closed

security(slack): isolate room text-command auth from DM pairing-store entries#26098
bmendonca3 wants to merge 6 commits intoopenclaw:mainfrom
bmendonca3:bm/security-7th-20260225

Conversation

@bmendonca3
Copy link
Copy Markdown
Contributor

@bmendonca3 bmendonca3 commented Feb 25, 2026

Summary

Slack room message command authorization still merged DM pairing-store entries into owner authorization checks.

In prepareSlackMessage, room/group command gating used allowFrom + readChannelAllowFromStore("slack"), so a user approved only through DM pairing-store state could become command-authorized for channel text commands (for example /new) without being explicitly in channels.slack.allowFrom or per-channel users.

This PR isolates room/group command authorization from DM pairing-store state.

Change Type

  • Security fix
  • Regression test

Scope

  • src/slack/monitor/message-handler/prepare.ts
  • src/slack/monitor/message-handler/prepare.test.ts

Security Impact

High. This is an AuthZ boundary bypass on a privileged command surface in shared channels.

Before fix:

  • room command auth considered DM pairing-store entries as owner authorization.
  • a DM-paired user could issue channel text control commands despite not being explicitly allowlisted for owner-level room authorization.

After fix:

  • DM pairing-store state remains DM-scoped.
  • room command authorization uses explicit owner/channel config only.

Repro + Verification

Deterministic repro (before patch):

  1. Checkout parent commit: git checkout 58eb15dd000c6aad16211d0492a238ba7f16ae51^
  2. Run:
    • pnpm exec vitest run src/slack/monitor/message-handler/prepare.test.ts --maxWorkers=1
  3. Observe failing test:
    • blocks channel control commands for senders authorized only via DM pairing store
    • pre-fix behavior returns prepared context with CommandAuthorized: true.

Post-fix verification:

  • pnpm exec vitest run src/slack/monitor/message-handler/prepare.test.ts src/slack/monitor/slash.test.ts --maxWorkers=1
  • expected: all pass.

Evidence

Dedupe checks:

Code evidence:

  • prepare.ts now derives room owner authorization from ctx.allowFrom only.
  • Added focused regression test for DM pairing-store-only sender attempting room control command.

Human Verification

  1. Configure Slack:
    • commands.useAccessGroups: true
    • channels.slack.allowFrom: ["U_OWNER"]
  2. Ensure attacker (U_ATTACKER) is only present in Slack pairing store (not in static allowFrom).
  3. In a Slack channel, send message containing a control command (for example mention + /new).
  4. Expected after fix: command path is blocked (no dispatch).

Compatibility / Migration

No config/schema migration.

Behavior change: operators who previously relied on DM pairing-store state for room command authorization must add explicit entries in channels.slack.allowFrom and/or channels.slack.channels.<id>.users.

Failure Recovery

Revert this commit to restore prior behavior if needed. Regression test remains usable to guard against future reintroduction of DM-to-room auth scope bleed.

Risks and Mitigations

Risk:

  • Existing deployments depending on DM pairing-store entries for room command authorization will see room command denials.

Mitigation:

  • Add intended room-authorized users explicitly to channels.slack.allowFrom and/or per-channel users allowlists.

Greptile Summary

This PR fixes an authorization boundary bypass where DM pairing-store entries leaked into room/group text-command authorization. Before the fix, prepareSlackMessage used resolveSlackEffectiveAllowFrom() (which merges ctx.allowFrom with readChannelAllowFromStore("slack")) for both DM access checks and room command gating. A user who was only approved through the DM pairing flow could issue privileged channel text commands (e.g., /new) without being explicitly listed in channels.slack.allowFrom or per-channel users.

The fix introduces ownerAllowFromLower, derived solely from ctx.allowFrom for room/group contexts, while preserving the full effective allow list for DM policy checks. This correctly scopes DM pairing-store state to DM access only.

  • Security fix: Room command authorization now uses explicit owner/channel config only, preventing DM-to-room auth scope bleed
  • Regression test: Adds a test verifying that a sender authorized only via DM pairing store is blocked from issuing channel control commands
  • Note: The analogous issue in the slash-command path (slash.ts) is tracked separately in PR security(slack): isolate room slash auth from DM pairing-store entries #26052

Confidence Score: 5/5

  • This PR is safe to merge — it is a focused, well-scoped security fix with a targeted regression test.
  • The change is minimal (3 lines of production code), surgically targets the correct authorization boundary, preserves existing DM behavior, and includes a clear regression test. The logic has been verified against the full authorization flow in auth.ts, allow-list.ts, and command-gating.ts. No regressions are introduced for DM, group DM, or channel message flows.
  • No files require special attention. Note that src/slack/monitor/slash.ts has a similar pattern (using effectiveAllowFromLower for room command auth) which is tracked separately in PR security(slack): isolate room slash auth from DM pairing-store entries #26052.

Last reviewed commit: 58eb15d

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Security follow-up linked from #26693 (comment)

Implemented in a46c92e8dc1ae34bfed336f7041fe37c294ba800:

  • Slack DM pairing-store reads now use account scope (readChannelAllowFromStore("slack", undefined, ctx.accountId)).
  • Slack DM pairing writes now include account scope (accountId: ctx.accountId / account.accountId).
  • Added regression assertions in Slack monitor tests.

@openclaw-barnacle openclaw-barnacle bot added size: M size: S agents Agent runtime and tooling and removed size: S size: M labels Feb 27, 2026
@openclaw-barnacle openclaw-barnacle bot added size: S and removed agents Agent runtime and tooling size: M labels Mar 1, 2026
@Takhoffman Takhoffman added the close:superseded PR close reason label Mar 1, 2026
@Takhoffman
Copy link
Copy Markdown
Contributor

Thanks for the contribution and the security focus here.

Closing this as superseded because the relevant auth-boundary hardening has already landed on main through newer changes, and merging this branch now would add duplicate or stale behavior.

If you want, we can open a fresh, narrowly-scoped follow-up PR on top of current main for any remaining gap.

@Takhoffman Takhoffman closed this Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack close:superseded PR close reason size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants