Repository navigation
v0.1.2
Third beta release of Loom — an evidence operating system for civil-rights legal teams that turns mixed field evidence (video, photos, audio, FOIA returns) into a defensible event timeline where every claim traces back to source material. Civil-liberties tooling: no face recognition, no suspicion scoring, no automated identity resolution.
Beta scope: prepared for first use in Illinois (state and federal § 1983 actions). Defaults are documented in
docs/requirements.mdand are jurisdiction-tunable.Not legal advice. Statutes and rules of evidence are referenced for design context only. Admissibility, consent, retention, and disclosure decisions for any specific matter belong with licensed counsel.
Release focus
v0.1.2 closes the "I forgot my password and have important data" gap on Desktop Lite. Single-user, offline installs have no SMTP path and no second admin to issue a reset, so a forgotten password historically meant either re-deriving the Argon2id hash by hand or losing every case in the install. This release adds two complementary recovery affordances — non-destructive (single-use codes) and destructive (factory reset) — so the operator always has an in-app path back into their install.
Highlights
- Password recovery via single-use codes. First-run onboarding now mints eight recovery codes alongside the bootstrap admin. The plaintext is displayed to the operator exactly once — the database only retains sha256 hashes — and each code can rotate the password exactly once via a new
POST /auth/recover-passwordendpoint (rate-limited 3/hour, no token issued so any active MFA enrollment still applies). Closes #133. - Factory reset from the login screen. Desktop Lite installs that have lost both the password and the recovery codes can now reset the install without quitting the app and hand-editing
%USERPROFILE%\.loom\data. The "Reset Loom" affordance on the login screen — gated to Lite + Tauri — opens a typed-confirmation dialog ("type RESET to continue") that wipesloom.dband thebuckets/subtree, clears the data-directory preference, and respawns the sidecar into a fresh first-run state. Bootstrap secrets are preserved so the install identity stays stable. Fixes #132. - First-run wizard gains a "save your recovery codes" step with copy-to-clipboard and download-as-
.txtactions, plus a typed acknowledgement gate so the operator cannot navigate past the codes without confirming they have stored them. - New
/forgot-passwordroute wired into the sign-in page (Lite-only link), with the same password complexity rules as registration so a recovered account never holds a weaker credential than a fresh one.
Design notes
#133 was a design issue weighing four options for password recovery: one-time codes, OS filesystem proof, change-password-only, and OS keychain integration. We picked option 1 (one-time codes) because:
- It's the cheapest path that actually solves the problem (a
secrets.token_hexcall, a sha256 hash check, and a Pydantic schema). - It has no platform matrix — Windows, macOS, and Linux Lite installs all use exactly the same code path. The OS keychain integration (option 4) would have been the strongest UX but the per-OS implementation and test surface aren't worth it for v0.1.x.
- It mirrors the existing MFA recovery-code pattern users may already recognize. The two surfaces remain in separate columns (
recovery_codesfor MFA,password_recovery_codesfor password recovery) so disabling or rotating one does not affect the other. - It composes cleanly with the destructive sibling: if the operator has lost both the password and every code, factory reset is the documented escape hatch.
The recovery flow intentionally does not issue tokens on success. The operator signs in normally on the next request, which means a leaked recovery code alone cannot bypass MFA — that surface still requires the second factor (or an MFA recovery code from the original 0.1.1 surface).
Migrations
- New Alembic revision
012_add_password_recovery_codesadds a nullablepassword_recovery_codes TEXTcolumn tousers. Existing accounts on upgraded installs have no codes (column is NULL); operators who want recovery codes on a pre-existing install can re-bootstrap (factory reset) or wait for the upcoming "rotate my codes" surface.
Install
- Desktop Lite (individuals): download the installer for your platform from this release's assets, or follow
docs/desktop-lite.mdto build from source. Existing installs upgrade in place; on first launch the migration runs and the new column lights up. Single-user, local-only — nothing leaves the machine unless you export. - Server (organizations): see
docs/prod-deploy.mdfor the production checklist (TLS, secrets, backup rotation, observability stack). The/auth/recover-passwordendpoint exists on every profile but the UI surface is Lite-only — server deploys keep their admin-reset paths.
Known limitations in 0.1.2
- No "rotate my codes" surface yet. Once you burn a code you can still recover with the remaining seven, but you cannot mint fresh ones without factory-reset + re-bootstrap. Tracked for v0.1.3.
- macOS Intel installer is still not produced. The CI matrix builds only
macos-latest, which resolves to Apple Silicon. Carryover from v0.1.1. - Desktop sidecar cold-start on Windows can still take ~10 s under the PyInstaller
--onefileextract-to-temp path with active antivirus.--onefile→--onedirmigration remains tracked. - No E2E browser tests in CI yet (#112) — Playwright smoke flow is tracked post-beta; golden-path browser flows continue to be exercised manually.
- Frontend test coverage gaps (#113) — roughly 14 hooks and 11 components without dedicated tests, tracked post-beta.
- Workflow E2E coverage is partial — ingest is exercised end-to-end (#119); correlation, scene, OCR, transcription, export, and url-ingest workflows still rely on structural assertions (#113).
Security
- Recovery codes are 80 bits of entropy each (20 hex chars). At the endpoint's 3/hour rate limit, exhaustive guessing of a single code would take longer than the heat-death of the sun.
- The recovery endpoint returns the same
401 invalid email or recovery codedetail for unknown emails, wrong codes, and deactivated accounts — no enumeration. - Factory reset preserves
secrets.jsondeliberately: rotating the bootstrap secrets would invalidate nothing useful once the database is gone, while keeping them stable bounds the blast radius if the deletion partially fails. - See
docs/security.mdfor the threat model and posture. - Report vulnerabilities privately by emailing the maintainer — do not open a public issue.
- One outstanding upstream advisory: CVE-2026-3219 (
pip≤ 26.0.1, no upstream patch yet) remains documented as tolerable_risk; pip is build-time only and supply-chain attacks on the upstream toolchain are out-of-scope per the threat model.