mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
792a5f6a72
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