Six new unit suites (102 tests) for the highest-risk untested libs:
userDataCleanup (destructive account/project deletes), documentVersions
(document integrity), chat/citations (legal-citation parsing), safeError
(secret redaction), llm/models (model resolution), and userLookup
(profile email sync). Coverage floors ratchet up 2/2/4/2 ->
11/10/14/10 (measured 11.18/10.98/14.43/10.91), and
docs/testing-coverage.md gives contributors a prioritized backlog for
the remaining untested libs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the amal66 fork (see index Open-Legal-Products/mike#205)
onto current main, adapted to this repo's backend/ layout
(apps/api/src/lib -> backend/src/lib), plus a v8 coverage ratchet.
Suites ported (51 new tests, verified locally):
- access.test.ts (7): owner/shared/private project access, doc access,
review sharing, document-ID filtering. Dropped the fork's "org RBAC"
describe block (7 cases) — org_id/org_members multi-tenancy and the
role/canManage fields do not exist in this repo's access.ts.
- storage.test.ts (25): filename normalization/sanitization, RFC 5987
encoding, Content-Disposition, storage key helpers. Dropped the fork's
vi.mock of lib/env — this repo has no env module; storage reads
process.env directly and the tested helpers are pure.
- userApiKeys.test.ts (10): normalizeApiKeyProvider + hasEnvApiKey.
Added a beforeEach env clear so shell-exported API keys can't leak
into assertions.
- chatTypes.test.ts (9): resolveDoc/resolveDocLabel, which live in
lib/chat/types.ts here (the fork's lib/chatTools.ts equivalent).
Dropped generateSpotlightNonce cases (2) — no such export here.
Suites dropped entirely (subject not present in this repo):
- upload.test.ts — tested hasMagicBytes; this repo's lib/upload.ts is
only the multer middleware and exports no magic-byte checker.
- userSettings.test.ts — tested resolveTabularModel (fork-only keyed-
provider fallback); this repo resolves tabular_model via
resolveModel with a static default.
Coverage ratchet: vitest.config.mts adds v8 coverage over src/lib/**
with floors measured against this tree (2.58% stmts, 2.00% branches,
4.61% funcs, 2.58% lines -> floors 2/2/4/2). Full suite: 5 files,
63 tests passing (incl. the pre-existing 12 in downloadTokens.test.ts);
npm run test:coverage and npm run build both pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from amal66/mike#24 onto current main; lockfiles regenerated against
this tree. Adds vitest as a dev dependency with a `test` script in both
packages, excludes test files from the backend tsc build, and seeds one
suite per package (backend: downloadTokens, 12 tests; frontend: cn() utils,
8 tests). Verified locally: backend 12/12, frontend 8/8 passing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add SecurityPage component for managing MFA settings, including enrollment and verification.
- Create MfaLoginGate to handle MFA verification state during login.
- Develop MfaVerificationPopup for user input of verification codes.
- Implement VerifyMfaPage for the MFA verification process after login.
- Introduce reusable VerificationCodeInput component for entering verification codes.
- Integrate Supabase MFA API for managing factors and verification.
- Add loading states and error handling for a better user experience.
- Refine CourtListener citation verification, bulk lookup logging, and API fallback behavior
- Persist cancelled chat stream output and render cancellation as the final assistant message
- Add document/version deletion safety fixes and shared warning/modal UI updates
- Sync document panel, case law panel, and response UI styling refinements
- Harden OSS sync script to preserve local env, dependency, and generated files
The tabular-review routes accept user-supplied document_ids in
request bodies (POST /tabular-review, PATCH /:reviewId) and stale
cell rows on byte-fetching paths (POST /:reviewId/regenerate-cell,
POST /:reviewId/generate). None of those paths checked whether the
caller can read those documents — a free-account attacker could plant
foreign UUIDs into their own review and have the server fetch the
bytes from R2 + run an LLM extraction over them, returning verbatim
text via the standard review GET.
Adds filterAccessibleDocumentIds(documentIds, userId, userEmail, db)
next to the existing access helpers (owner-of-doc OR project member),
and applies it at the four entry points:
- POST /tabular-review drop unauthorised on insert
- PATCH /:reviewId drop newly-added unauthorised; keep
already-attached cells so non-owner
collaborators don't accidentally
orphan rows they can't directly
access
- POST /:reviewId/regenerate-cell refuse byte fetch when caller has
no access to the underlying doc
- POST /:reviewId/generate filter docIds before parallel LLM
fetch (defense-in-depth for legacy
cells planted before this fix)
Fails closed silently rather than 403'ing so legacy clients that pass
stale ids don't error out the whole review.
Detected by Aeon + manual review.
Severity: high
CWE-639 (Authorization Bypass Through User-Controlled Key)
Resolves the issue where getSecret() silently fell back to the literal
string "dev-secret" when neither DOWNLOAD_SIGNING_SECRET nor
SUPABASE_SECRET_KEY was set. Because the codebase is public, that
fallback let anyone forge valid /download/:token signatures against a
mis-configured deployment.
- Throw at first call instead of returning the hardcoded string, with a
message pointing the operator at `openssl rand -hex 32`.
- Document DOWNLOAD_SIGNING_SECRET in backend/.env.example so deployers
following the README know to set it (and that it should be distinct
from SUPABASE_SECRET_KEY).
Closes#7