# Privacy & Security

LatticeSearch is built to be the anti-cloud, anti-Recall alternative: it indexes only what you point
it at, and nothing ever leaves the machine. This document describes the security model **honestly** —
including where the boundary really is.

## Local-only by construction

- **No cloud.** Every model (CLIP, e5, CLAP, the reranker, Florence, OCR, faces) is bundled on disk
  and loaded with `HF_HUB_OFFLINE=1`; embedding, search, transcription, and OCR all run on your CPU
  (GPU only as an optional local speed-up). Model bundling is verified to make **zero network calls**.
- **No telemetry.** The engine does not phone home, does not collect usage, and does not upload your
  files or index. There is no analytics endpoint.
- **The index is yours.** Names, embeddings, and doc chunks live in files beside the engine on your
  disk. Nothing is synced anywhere.

The only optional outbound call in the whole product is license status/revocation in the shipping
build (a single `GET` of a `{revoked}` boolean, no files or content, fail-open with a 14-day offline
grace) — never part of search.

## Network posture (why a local HTTP server is still safe)

The engine is an HTTP server on `127.0.0.1`, which raises the obvious question: can a random web page
you visit reach it? The answer is no, on three independent layers:

1. **Localhost bind.** The server binds `127.0.0.1` only — it is not reachable from the network, only
   from processes on this machine.
2. **Origin rejection.** Every `/api/` request from a foreign origin is refused with `403`. A
   malicious page's `fetch()` carries its `Origin`; the engine rejects any origin that isn't its own.
   (Plain `<img>`/`<video>` tags send no `Origin` but can only reach read endpoints, which the
   browser's same-origin policy keeps unreadable to the attacking page anyway.)
3. **Mutating-endpoint token.** Every state-changing endpoint (`/api/fileop`, `/api/ops`,
   `/api/trash`, `/api/restore`, `/api/upload`, `/api/star`, `/api/prefs`, `/api/transfer`,
   `/api/backup`, `/api/index_start`, …) requires the `X-LS-Token` header. The token is a random
   `secrets.token_hex(16)` generated on first start and stored in `ls_token.txt` beside the engine —
   readable only by processes that can read that file (your own tools and the injected UI), never by
   a web page. This closes the CSRF-style hole where a page fires requests at `127.0.0.1`.

## Incognito mode

Incognito is a session toggle (`/api/incognito`, or "Toggle Incognito" in the UI; can be defaulted-on
via preferences). When on, the lattice **records nothing about what you do** — exactly the following
signals are silenced:

| Silenced in Incognito | Normal behaviour |
|---|---|
| **Recent searches** | committed searches are added to `recents` |
| **Activity log** | file ops (rename/move/copy/delete) are appended to `activity.json` |
| **Memory store** | `memory_store` is refused with a clear `403` ("nothing is recorded") |
| **Clipboard capture** | clipboard items are captured to the searchable history |
| **Search cache** | query results are cached in-process for repeat speed |
| **Jitter labels** | slow-query diagnostics record the query text; in Incognito the label is just `(incognito)` |

**What Incognito does *not* do — by design:** it does **not** turn deletes into hard deletes. A file
you delete in Incognito is still archived to the recovery store (and the Recycle Bin), because safe,
reversible deletion is a core safety guarantee, not an activity trail. Incognito is about not
*recording your behaviour*, not about defeating undo. If you truly want bytes gone, use a hard
delete outside the tool.

## True-forget grace window

Removing a recent search (or clearing all recents) does not silently erase it forever the instant you
click. Instead the entry is marked **forget-pending** with a timestamp:

- If you **search it again within the grace window** (`forget_days`, default **7 days**), it is
  *un-forgotten* — the common "I removed it but I actually still use it" case is handled gracefully.
- If the grace window passes with no re-search, the entry is genuinely dropped.

This avoids both extremes: nothing lingers forever, and nothing you actually still use disappears on a
misclick.

## Clipboard secret filter

Clipboard capture (when enabled and not in Incognito) deliberately refuses to store credential
material. Text that looks like a secret is **counted but never stored, never indexed, never passed
on**. The filter catches:

- AWS-style access keys,
- `password` / `passwd` / `token` / `secret` adjacent to a `:` or `=` (so `DB_PASSWORD=…` and
  `MY_API_TOKEN: …` both match — no word boundary, on purpose),
- long base64 runs,
- PEM blocks (`-----BEGIN … KEY-----`).

Skipped items are tallied in a `skipped_secrets` counter (visibility that the filter is working) and
otherwise discarded. Empty and oversized (>100 KB) clips are skipped too.

## The honest boundary — a privacy feature, not anti-forensics

The lattice leaves **no trace of your search/activity inside the lattice** when you use Incognito and
the forget/secret filters. That is the whole promise, and it is real.

What it **cannot** do — and does not claim to — is scrub the operating system. Windows independently
journals file activity in the **USN change journal**, the **MFT**, prefetch, and the **event logs**;
macOS/Linux have their own equivalents. LatticeSearch does not (and should not) tamper with any of
that. Think of Incognito like a browser's incognito mode: it stops *this application* from keeping a
history, which is a genuine and useful privacy property — it is **not** anti-forensics, and nobody
should treat "used Incognito" as "left no evidence on the machine."

## Multi-user

- **Windows ACLs are respected.** The indexer scans via ordinary directory reads and only ever sees
  what the running user is permitted to read. A non-admin install falls back to a normal directory
  walk (slower first scan, no raw-volume MFT/USN), which is exactly right for a work laptop: other
  users' protected folders are never touched because the OS won't hand them over.
- **Per-user index.** The index reflects one user's accessible view of the disk; it is not a shared
  system-wide database of everyone's files.
- **Roadmap:** first-class **per-user instances** (isolated index + token + preferences per Windows
  account) are planned so a shared machine keeps each user's lattice fully separate.

## Vault — searchable encrypted archive

For data that must be protected at rest, **Vault mode** packages files *and* their index into a
single encrypted blob:

- **AES-256-GCM** authenticated encryption for both the files and the search index.
- Key derived from your passphrase with **scrypt** (N = 2¹⁵) — no key is stored.
- Plaintext and even filenames are absent from the blob; a wrong passphrase seals it; GCM tamper
  detection rejects any modified bytes (`InvalidTag`).
- **Cross-lingual semantic search runs *inside* the sealed vault** — you can search encrypted
  documents by meaning, in any language, without decrypting them to disk first. Extraction is
  byte-perfect.

This is something a cloud service structurally cannot offer: searchable, local, passphrase-held
encryption where the provider never sees the key or the content.

---

See [MCP.md](MCP.md) and [CLI.md](CLI.md) for the interfaces these protections apply to, and
[DEVELOPING.md](DEVELOPING.md) for how the engine and its warm model host are run locally.
