Skip to content

fix(security): prevent cross-org KV data leak for multi-org users#4018

Merged
pedrofrxncx merged 1 commit into
mainfrom
fix/cross-org-kv-data-leak
Jun 22, 2026
Merged

fix(security): prevent cross-org KV data leak for multi-org users#4018
pedrofrxncx merged 1 commit into
mainfrom
fix/cross-org-kv-data-leak

Conversation

@0xcucumbersalad

@0xcucumbersalad 0xcucumbersalad commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Remove legacy unscoped /api/kv/:key routes — these lacked resolveOrgFromPath middleware, so org context was resolved via the MCP OAuth fallback path which uses .executeTakeFirst() without ORDER BY. For multi-org users without an x-org-id header, PostgreSQL could return any membership row, causing reads/writes to land in the wrong org's KV namespace. The org-scoped version at /api/:org/kv/:key (mounted in org-scoped.ts with proper middleware) already exists and is unaffected.
  • Fix MCP OAuth org resolution fallback — when no x-org-id/x-org-slug header is provided, the fallback now only resolves an org when the user has exactly one membership. Multi-org users without a hint get organization: undefined (resulting in a 400 "Organization required" from any org-scoped endpoint) instead of a non-deterministic pick.

Files changed

File Change
apps/mesh/src/api/app.ts Remove legacy /api/kv/:key mount and unused import
apps/mesh/src/core/context-factory.ts Replace bare .executeTakeFirst() with limit(2).execute() — return the row only when exactly 1 membership exists

Test plan

  • bun run check — passes (no type errors)
  • bun run fmt — passes
  • bun run lint — passes (0 errors)
  • bun test apps/mesh/src/core/ — 57 pass, 4 fail (pre-existing integration test failures on main due to no local PostgreSQL)
  • Verify GET /api/kv/:key returns 404 (route removed)
  • Verify GET /api/:org/kv/:key works correctly with resolveOrgFromPath
  • Verify multi-org MCP OAuth user without x-org-id header gets organization: undefined → 400 on org-scoped endpoints

🤖 Generated with Claude Code


Summary by cubic

Prevents cross‑org KV access for multi‑org users by removing the unscoped /api/kv/:key and hardening MCP OAuth org resolution. KV is now org‑scoped only; multi‑org requests without an org hint return 400, and removed users no longer get SSO sessions.

  • Bug Fixes

    • In MCP OAuth fallback, only resolve an org when the user has exactly one membership; otherwise leave org undefined.
    • Re‑verify org membership in SSO /callback before creating a session; redirect with sso_error=not_a_member if not a member.
  • Migration

    • Replace /api/kv/:key with /api/:org/kv/:key.
    • For multi‑org users, include x-org-id or x-org-slug; otherwise org‑scoped endpoints return 400.

Written for commit be14453. Summary will update on new commits.

Review in cubic

The legacy unscoped `/api/kv/:key` routes lacked `resolveOrgFromPath`
middleware. Combined with a `.executeTakeFirst()` fallback (no ORDER BY)
in the MCP OAuth org-resolution path, multi-org users without an
`x-org-id` header could non-deterministically read/write KV data from
the wrong organization.

Two changes:
1. Remove the legacy `/api/kv/:key` mount — the org-scoped version at
   `/api/:org/kv/:key` already exists and is properly guarded.
2. In the MCP OAuth fallback (context-factory.ts), when no org hint is
   provided, only resolve the org when the user has exactly one
   membership. Multi-org users without a hint now get `organization:
   undefined` instead of a random pick.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@0xcucumbersalad 0xcucumbersalad force-pushed the fix/cross-org-kv-data-leak branch from 7fcb429 to be14453 Compare June 19, 2026 07:03
@pedrofrxncx pedrofrxncx merged commit 1ddbd93 into main Jun 22, 2026
29 checks passed
@pedrofrxncx pedrofrxncx deleted the fix/cross-org-kv-data-leak branch June 22, 2026 01:47
decocms Bot pushed a commit that referenced this pull request Jun 22, 2026
PR: #4018 fix(security): prevent cross-org KV data leak for multi-org users
Bump type: patch

- decocms (apps/mesh/package.json): 3.37.1 -> 3.37.2

Deploy-Scope: server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants