[LegalMind] getDocuments storage method does not enforce user ownership — relies entirely on route-layer checks
storage.getDocuments(projectId) (server/storage.ts:269) returns all documents for a project without verifying the caller owns the project. Every one of the 20+ route callers mus...
StateDoneNext ActionClosedOwnerLuciRuntimeClosedAge27d ago
Ticket is done; runtime is closed.·profile claude_opus_1m_medium · cwd /home/lucienne/workspace · uptime 27d 3h · last activity 27d 1h ago
Description
MC-3570
storage.getDocuments(projectId) (server/storage.ts:269) returns all documents for a project without verifying the caller owns the project. Every one of the 20+ route callers must independently fetch the project and compare userId — a pattern that is correctly implemented today but has no defense-in-depth at the storage layer. Adding a userId parameter (or a separate getDocumentsForUser method) would prevent accidental data leakage if a future caller forgets the ownership check.
File: server/storage.ts:269
Complexity: medium
Found by: nightly code review (e49ba03c fix(MC-3526): scope role-phrase stripping to user instruction fields)
Activity
done
INTERACTIVE
Luci is working...
Details —
Done
· Low
· Luci
▼
SState
Done
Closed
PPeople
TTiming / Details▼
legalmind-review (scheduled)
LegalMind
27d ago
27d ago
Advanced / Operator evidence
RRouting owner
ROperator console
Ticket is done; runtime is closed.completedprofile claude_opus_1m_medium · cwd /home/lucienne/workspace · uptime 27d 3h · last activity 27d 1h agoMC is visibility-only. Hermes Luci launches and gates work outside MC, then mirrors evidence/status here.
a98700ff-1587-44a5-945d-2573f2cef10a · luci · claude/anthropic/opus[1m]
WWorkflow
Start Dev Review + QA ▾
Select phases to include:
Agents
Review Gates
Decision
WAT routing: choose an agent, review gate, or decision. Buttons use the live runtime when one is attached.
Luci dispatching this ticket to Larry (background worker via SSH).
luci27d ago
Larry: Planning — reading ticket and storage layer to scope the changePROGRESS: Designing — add optional userId param; verify ownership via JOIN; update route callers, leave internal/cron callers unchangedPROGRESS: Building — adding userId parameter to storage.getDocuments with ownership check + INNER JOINPROGRESS: Building — writing test for MC-3570 then implementing storage changeNow implement the storage changes.Now add the error class. Let me find a good location.PROGRESS: Testing — running typecheck and the new testPROGRESS: Building — wiring userId into route callers that already have it in scopeNow the documents.ts and fileSearch.ts callers.PROGRESS: Testing — typecheck + relevant tests after wiring userId into route callersAll failures pre-existing and unrelated to my change. Committing.
luci27d ago
Larry: Review gate — running pre-landing reviewPROGRESS: Done — committed MC-3570 with storage-layer ownership enforcement + caller wiring + test
luci27d ago
Completed on Larry:
Added optional `userId` parameter to `storage.getDocuments` — when provided, verifies project ownership via a projects-table lookup and throws a typed `ProjectAccessDeniedError` on mismatch. Wired `userId` through 13 route call sites that already had it in scope (projects, documents, fileSearch, chatTimeline, reports, customFields, documentContent, batchAnalysis, api-v1). Internal/system callers (syncService, indexer) keep the unscoped signature. New test `mc3570-getdocuments-ownership.test.ts` passes; pre-existing unrelated suite failures confirmed unchanged. Committed as `79bafdf`.
luci27d ago
Larry: Triggering post-completion code review...
luci27d ago
Code review (Larry reviewer via claude/opus):
missed route-level check would surface as a 500 instead of a 403. Acce
ptable as the route checks still exist (the throw is the
safety net), but a centralized error handler mapping this to 403 would close
the loop.
MINOR: `server/storage.ts:292` — returning `[]` when the project doesn't exist (with userId prov
ided) silently diverges from the non-userId branch which would also return `[]` (no matching documents), so beh
avior is consistent but slightly opaque. Fine.
MINOR: Test asserts impl
ementation details via regex on source text rather than behavior. Brittle to ref
actor, but adequate for a defense-in-depth invariant.
VERDICT: PASS
Defense-in-depth ownership check at storage layer, wired through authenticated route callers; tests pass.
MINOR: `server/storage.ts:291` — `ProjectAccessDeniedError` is thrown but no route catches it, so a missed route-level check would surface as a 500 instead of a 403. Acceptable as the route checks still exist (the throw is the safety net), but a centralized error handler mapping this to 403 would close the loop.
MINOR: `server/storage.ts:292` — returning `[]` when the project doesn't exist (with userId provided) silently diverges from the non-userId branch which would also return `[]` (no matching documents), so behavior is consistent but slightly opaque. Fine.
MINOR: Test asserts implementation details via regex on source text rather than behavior. Brittle to refactor, but adequate for a defense-in-depth invariant.
VERDICT: PASS
luci27d ago
Larry shipped with clean code review. Auto-dispatching Luci-reviewer (verdict: clean). Next dispatch tick will spawn a single-session reviewer worker to verify, run Tessa subagent inline if UI-touching, merge the PR, and close.
luci27d ago
Completed:
DONE: MC-3570 redone — the prior `79bafdf` commit was never pushed (its /tmp clone was deleted), so I reimplemented on a fresh LegalMind clone. Added optional `userId` param to `storage.getDocuments` with a projects-table ownership lookup that throws the new typed `ProjectAccessDeniedError` on mismatch (defense-in-depth); wired `userId` through 16 authenticated route call sites; internal callers (syncService, indexer, extractionHelpers) keep the unscoped signature. New test `mc3570-getdocuments-ownership.test.ts` passes (6 tests), `tsc` error count unchanged (29 pre-existing), full vitest failing-file set identical to baseline (zero regressions). Pushed branch `autofix/mc-3570-getdocuments-ownership`, opened PR #414 (https://github.com/conrelma/LegalMind-Explorer/pull/414) for GitHub CI.
luci-operator27d ago
Luci Operator: promoted this assigned `inbox` ticket to `todo` so pickup can run it.