Skip to content

Critical: GET /api/content-items is missing domain filter and leaks cross-tenant content #145

@dligthart

Description

@dligthart

Summary

GET /api/content-items does not scope queries by domainId, which breaks the project’s tenant isolation guarantee and allows cross-tenant listing/count leakage.

Evidence

In src/api/routes.ts:

  • Lines 1628-1633 build filters, but no eq(contentItems.domainId, getDomainId(request)) is included.
  • Lines 1636-1640 run count(*) and list queries with that unscoped whereClause.

Relevant snippet:

  • src/api/routes.ts:1628
  • src/api/routes.ts:1636

Impact

  • Any authenticated tenant can enumerate content items from other tenants by using broad filters.
  • Response metadata (total, hasMore) also leaks cross-tenant cardinality.
  • This directly contradicts doc/concepts/features.md multi-tenant contract (All REST, GraphQL, and MCP APIs filter strictly by the domainId ...).

Proposed Fix

  1. Add eq(contentItems.domainId, getDomainId(request)) to the conditions array for the route.
  2. Ensure both count(*) and result queries use the scoped predicate.
  3. Add regression tests in src/__tests__/api-tenant.test.ts for:
    • list isolation across two domains
    • metadata isolation (total, hasMore)

Acceptance Criteria

  • Domain A API key cannot observe Domain B content items or counts via /api/content-items.
  • New regression tests fail on current code and pass after fix.
  • Multi-tenant feature docs remain true for this route.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions