Skip to content

security(discord): scope DM preflight/native pairing auth to accountId#26696

Closed
bmendonca3 wants to merge 6 commits intoopenclaw:mainfrom
bmendonca3:bm/discord-dm-preflight-native-account-scope-20260225-093211
Closed

security(discord): scope DM preflight/native pairing auth to accountId#26696
bmendonca3 wants to merge 6 commits intoopenclaw:mainfrom
bmendonca3:bm/discord-dm-preflight-native-account-scope-20260225-093211

Conversation

@bmendonca3
Copy link
Copy Markdown
Contributor

@bmendonca3 bmendonca3 commented Feb 25, 2026

Summary

This PR fixes a Discord auth boundary bypass where DM pairing-store authorization in high-traffic paths was not scoped by account:

  • src/discord/monitor/message-handler.preflight.ts
  • src/discord/monitor/native-command.ts
  • src/discord/monitor/agent-components.ts

In multi-account Discord setups, pairing approvals created under one account could be consulted while authorizing DM senders under a different account.

Change Type

  • Security fix
  • Regression tests

Scope

  • src/discord/monitor/message-handler.preflight.ts
  • src/discord/monitor/native-command.ts
  • src/discord/monitor/agent-components.ts
  • src/security/audit-channel.ts
  • src/discord/monitor/message-handler.preflight.account-scope.test.ts
  • src/discord/monitor/native-command.account-scope.test.ts
  • src/discord/monitor/monitor.test.ts

Security Impact

  • Trust boundary: channels.discord.accounts.<accountId>.
  • Before: DM preflight/native/component auth read or wrote pairing state in channel-global scope.
  • After: all three paths use accountId scope for pairing store reads/writes.
  • Practical impact: prevents cross-account DM authorization bleed between Discord accounts on one gateway.

Repro + Verification

Deterministic repro tests:

  • src/discord/monitor/message-handler.preflight.account-scope.test.ts
  • src/discord/monitor/native-command.account-scope.test.ts

Pre-fix validation (executed against upstream versions of patched files):

  • pnpm exec vitest run src/discord/monitor/message-handler.preflight.account-scope.test.ts src/discord/monitor/native-command.account-scope.test.ts --maxWorkers=1
  • Result: failing assertions showed unscoped pairing-store reads.

Post-fix verification:

  • pnpm check
  • pnpm exec vitest run src/discord/monitor/message-handler.preflight.account-scope.test.ts src/discord/monitor/native-command.account-scope.test.ts src/discord/monitor/message-handler.preflight.test.ts src/discord/monitor/monitor.test.ts src/security/audit.test.ts --maxWorkers=1
  • Result: pass.

Evidence

Dedupe search:

Human Verification

  • Confirmed DM preflight reads pairing allowlist via:
    • readChannelAllowFromStore("discord", undefined, params.accountId)
  • Confirmed native command DM path reads pairing allowlist via:
    • readChannelAllowFromStore("discord", undefined, accountId)
  • Confirmed component interaction path reads pairing allowlist via:
    • readChannelAllowFromStore("discord", undefined, ctx.accountId)
  • Confirmed all corresponding pairing upserts include accountId.
  • Confirmed Discord audit reads pairing allowlist per account:
    • readChannelAllowFromStore("discord", undefined, accountId)

Compatibility / Migration

  • No config migration.
  • Existing account-scoped approvals keep working.
  • Behavior is stricter and isolated per Discord account.

Failure Recovery

  • Revert this PR to restore previous behavior.
  • If needed, regenerate approvals per account through normal DM pairing flow.

Risks and Mitigations

  • Risk: users previously relying on cross-account pairing bleed may now be denied.
    • Mitigation: intended hardening; approvals should be per-account boundary.
  • Risk: future Discord DM authorization paths could miss account scope.
    • Mitigation: focused regression tests cover preflight and native command flows, plus component assertions.

@openclaw-barnacle openclaw-barnacle bot added the channel: discord Channel integration: discord label Feb 25, 2026
@bmendonca3
Copy link
Copy Markdown
Contributor Author

@greptileai please run security/code review on this PR.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 25, 2026

Additional Comments (2)

src/discord/monitor/agent-components.ts
same auth boundary bypass exists here - missing accountId parameter

PR description claims #26671 fixes this, but that PR appears to be open/future work. This path has the identical vulnerability that was just fixed in message-handler.preflight.ts and native-command.ts.

should be:

    dmPolicy === "allowlist" ? [] : await readChannelAllowFromStore("discord", undefined, ctx.accountId).catch(() => []);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/agent-components.ts
Line: 475

Comment:
same auth boundary bypass exists here - missing `accountId` parameter

PR description claims #26671 fixes this, but that PR appears to be open/future work. This path has the identical vulnerability that was just fixed in `message-handler.preflight.ts` and `native-command.ts`.

should be:
```suggestion
    dmPolicy === "allowlist" ? [] : await readChannelAllowFromStore("discord", undefined, ctx.accountId).catch(() => []);
```

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

src/discord/monitor/agent-components.ts
missing accountId in pairing write

consistent with the other fix locations, this should include:

    const { code, created } = await upsertChannelPairingRequest({
      channel: "discord",
      id: user.id,
      accountId: ctx.accountId,
      meta: {
        tag: formatDiscordUserTag(user),
        name: user.username,
      },
    });
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/agent-components.ts
Line: 494-501

Comment:
missing `accountId` in pairing write

consistent with the other fix locations, this should include:
```suggestion
    const { code, created } = await upsertChannelPairingRequest({
      channel: "discord",
      id: user.id,
      accountId: ctx.accountId,
      meta: {
        tag: formatDiscordUserTag(user),
        name: user.username,
      },
    });
```

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

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed Greptile feedback from #26696 (comment) in commit fe47d32fc8.

Applied in src/discord/monitor/agent-components.ts:

  • readChannelAllowFromStore("discord", undefined, ctx.accountId)
  • upsertChannelPairingRequest({ ..., accountId: ctx.accountId })

Also added regression assertions in src/discord/monitor/monitor.test.ts.

@bmendonca3
Copy link
Copy Markdown
Contributor Author

@greptileai please rerun on the latest head commit after addressing the agent-components account-scope findings.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 25, 2026

Additional Comments (1)

src/security/audit-channel.ts
missing accountId scope - audit is iterating per-account (line 188) but reading unscoped store

        const storeAllowFrom = await readChannelAllowFromStore("discord", undefined, accountId).catch(() => []);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/security/audit-channel.ts
Line: 227

Comment:
missing `accountId` scope - audit is iterating per-account (line 188) but reading unscoped store

```suggestion
        const storeAllowFrom = await readChannelAllowFromStore("discord", undefined, accountId).catch(() => []);
```

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

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed follow-up Greptile finding from #26696 (comment) in commit d59a5863d4.

Updated src/security/audit-channel.ts to read Discord pairing store with account scope:

  • readChannelAllowFromStore("discord", undefined, accountId)

@bmendonca3
Copy link
Copy Markdown
Contributor Author

@greptileai please rerun on the latest head commit after addressing the audit-channel account-scope finding.

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed Greptile notes:

Implemented in commit d59a5863d45ae74165d7f20b848b78b724ba9b28:

  • src/discord/monitor/agent-components.ts
    • readChannelAllowFromStore("discord", undefined, ctx.accountId)
    • upsertChannelPairingRequest(... accountId: ctx.accountId ...)
  • src/security/audit-channel.ts
    • account-scoped Discord audit read: readChannelAllowFromStore("discord", undefined, accountId)

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed the failing check CI job on this PR in commit cdc60c1833.

Fix is test-only and keeps behavior unchanged:

  • corrected mocked module exports in account-scope regression tests
  • used explicit unknown bridge casts required by strict TS checks

Verified locally:

  • pnpm check passes
  • targeted Discord/security regression suite passes (126/126)

@bmendonca3
Copy link
Copy Markdown
Contributor Author

@greptileai please rerun security/code review on the latest head commit cdc60c1833.

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: M size: S and removed size: S agents Agent runtime and tooling size: M labels Feb 27, 2026
@steipete
Copy link
Copy Markdown
Contributor

steipete commented Mar 2, 2026

Thanks for the PR! Multiple PRs address the same fix. Keeping #26671 as the earliest submission. Closing to reduce noise. This is an AI-assisted triage review. If we got this wrong, feel free to reopen — happy to revisit.

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

Labels

channel: discord Channel integration: discord size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants