Skip to content

v0.1.2

Choose a tag to compare

@jrwinget jrwinget released this 13 May 23:33
· 27 commits to main since this release
2879a19

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.md and 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-password endpoint (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 wipes loom.db and the buckets/ 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-.txt actions, plus a typed acknowledgement gate so the operator cannot navigate past the codes without confirming they have stored them.
  • New /forgot-password route 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_hex call, 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_codes for MFA, password_recovery_codes for 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_codes adds a nullable password_recovery_codes TEXT column to users. 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.md to 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.md for the production checklist (TLS, secrets, backup rotation, observability stack). The /auth/recover-password endpoint 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 --onefile extract-to-temp path with active antivirus. --onefile → --onedir migration 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 code detail for unknown emails, wrong codes, and deactivated accounts — no enumeration.
  • Factory reset preserves secrets.json deliberately: 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.md for 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.

Documentation