Files
Claude 792a5f6a72 test: Red team tests for documents.list filter DSL permission bypass
Adds extensive tests that probe the new filter DSL in #12176 for
permission-bypass vectors. Several tests intentionally fail to surface a
real authorization bypass in the documents.list handler:

`hasExplicitCollectionId` walks the filter tree recursively and treats
any `collectionId eq/in` reference — including ones nested inside an OR
group — as an "explicit" collection target. When that helper returns
true, documents.ts drops the default `collectionId IN user.collectionIds`
scope. But the actual WHERE clause is built honoring the OR semantics,
so an OR'd sibling expands the result set across the entire team within
the OR's reach.

Concrete exploits demonstrated by failing tests:

  filters: [{ operator: "OR", filters: [
    { field: "collectionId", operator: "eq", value: <my collection> },
    { field: "title", operator: "contains", value: "" },
  ]}]

returns every document in the team — the "" contains pattern matches
all rows. Variants tested:

  - OR(collectionId eq, title contains "")
  - OR(collectionId eq, title contains "secret")
  - OR(collectionId eq, userId eq <teammate>)
  - OR(collectionId in [...], title contains "")
  - OR(collectionId nested in AND, title contains "")
  - OR(collectionId eq, documentId eq <secret doc>)
  - AND(OR(collectionId eq, title contains ""))
  - OR(parentDocumentId eq <member doc>, collectionId eq <my collection>)
    — drops collection scope and exposes the parent's children that live
    in collections the user otherwise can't see

The cross-team filter (teamId at the root of the where) still applies,
so this bypass is bounded to documents within the same team — but it
exposes documents from collections the requesting user has zero
permission to access.

Defensive cases that pass and lock in the existing safety contract:

  - cross-team isolation
  - direct documentId lookup respects collection scope
  - documentId in[] respects collection scope
  - templateId filter respects collection scope
  - archivedAt isNotNull does not surface archived docs in private
    collections
  - userId filter does not bypass collection scope
  - drafts of other users are not exposed
  - collectionId neq/notIn/isNotNull/contains/startsWith do not drop
    the default scope (some return SQL error 5xx instead, no leak)
  - authorize() runs for every collectionId referenced in the tree,
    including nested in[] entries
  - parent-doc membership escape (single top-level eq) still narrows
    results to actual children
  - schema rejects OR groups at the top of search and search_titles
  - schema rejects depth/group-size/in-list overruns and unknown ops
  - LIKE wildcards (%, _, \) are escaped in contains/startsWith/endsWith
  - ISO 8601 duration validation rejects SQL-quote injection attempts

Helper-level unit tests in Filters.test.ts pin the same invariants at
the function boundary, including two intentionally failing assertions
that hasExplicitCollectionId must NOT trip on OR-nested collectionId
references.

https://claude.ai/code/session_0162e757xJkcqFzX8J2XX9ib
2026-05-10 19:02:48 +00:00
..
2026-05-06 21:10:51 -04:00