Aletheia

Privacy and redaction in Aletheia

Aletheia (aletheia-mem) stores your coding-session memory in a local SQLite file, listens on the loopback address only, has no telemetry and no account, and removes secrets and <private> blocks from captured text before it stores it. Redaction is pattern based, so it is not complete.

What stays on your machine by default

{"kind":"memory","memory":{"project":"w","tool":"claude-code","sessionId":"s7","kind":"prompt","type":"note","title":"deploy with token=[redacted] now","narrative":"deploy with token=[redacted] now"},"at":1793303598934}

That line is the spool content after a hook ran against an unwritable database path. The secret was removed before it reached the disk.

What is stored where

The data directory is ~/.aletheia, or ALETHEIA_DATA_DIR. On POSIX systems Aletheia sets owner-only modes when it opens the store, and it prints a warning when it has to tighten one:

aletheia: data directory .../d mode 0775 -> 0700
aletheia: file .../d/hub.db mode 0644 -> 0600
700 d
600 d/hub.db
600 d/erasure.key
600 d/erasure-receipts/90cc8cbbd16c69ba.json
664 d/erasure-exports/erase-3b2071736f7713d7.jsonl

The database, its -wal and -shm files, the spool and the erasure key and receipts get mode 0600. The erasure export (see below) was 0664, and a few other database files such as alerts.db were 0644. The 0700 data directory keeps other users out of all of them. On Windows the mode step is skipped, and Windows' own file permissions (ACLs) apply.

hub.db holds the memories, facts, rules and settings. Everything is plain SQLite with no encryption at rest. Use disk encryption if you need it.

What redaction removes

Before a captured text is stored, Aletheia first replaces every <private>...</private> block with [private]. Then it replaces text that matches these patterns with a marker such as [redacted:github-token]:

The patterns are in redact.mjs. Real results:

"git clone https://user:hunter2pass@example.com/repo.git"  ->  "git clone https://user:[redacted]@example.com/repo.git"
"Authorization: Bearer abcdefghijklmnop1234"               ->  "Authorization: Bearer [redacted]"
"ghp_abcdefghijklmnopqrstuvwxyz0123456789"                 ->  "[redacted:github-token]"
"Use <private>my home address is 12 Elm St</private> for the demo"  ->  "Use [private] for the demo"

Which paths redact

Every write ends in the store's add() or update(), and both redact. That covers hooks (aletheia hook), aletheia capture, aletheia save, POST /v1/events, POST /v1/memories, the note box in the dashboard (POST /api/hub/memories), edits (PATCH /api/hub/memories/<id>), the MCP save_memory tool, transcript import, aletheia watch, aletheia import bundle and aletheia import claude-mem. Rule evidence quotes are redacted too. Fact extraction redacts again, once before it builds a prompt for a provider and once more before it stores each fact. Sync redacts every payload before it leaves the device.

Two things are not redacted: the text of a rule (aletheia rules lint warns when a rule looks like it holds a secret) and rows stored by an earlier version. Before this change, notes written through REST or the dashboard, edits and imports were stored as given. Find and fix those rows:

aletheia scrub            # count the rows that redaction would change
aletheia scrub --apply    # redact them in place (back up hub.db first)

Limits of redaction

Redaction is pattern based. It misses secrets that do not look like one of the patterns above:

Do not rely on redaction for material that must never be stored. Keep secrets in the environment, and exclude a project you do not want captured by setting ALETHEIA_EXCLUDED_PROJECTS=name1,name2 in the environment the host runs in.

The Settings screen lists "Redact before storing" and "Excluded projects". The capture code does not read either value. Redaction is always on in the paths listed above, and exclusion works through the environment variable.

What leaves your machine

Nothing leaves by default. The code reaches the network only in these cases, each of which you start:

Erase data with a receipt

aletheia erase removes memories, observations, episodes, facts, summaries and rule suggestions. Choose what to erase with --project, --id or --before. It first reports what it would touch, using ids and counts and never text:

aletheia erase --id 14
Erasure report for ids memory:14

  memories              1  ids 14
  observations          0
  episodes              0
  facts                 0
  summaries             0
  rule suggestions      0

Nothing was deleted. Add --apply to erase, export first with --export <file>.
Report only. Add --apply to erase.

The plan follows provenance. A memory's episode, an observation's facts and a fact's episode all join the same erasure. With --apply, Aletheia writes an export, deletes the rows in one transaction, purges the matching sync entities and device rows when a sync store exists, and writes a signed receipt:

aletheia erase --id 14 --apply
aletheia erase verify <receipt.json>
...
Pre-erase export: .../d/erasure-exports/erase-3b2071736f7713d7.jsonl
Erased {"memories":1,"observations":0,"episodes":0,"facts":0,"summaries":0,"ruleSuggestions":0}; sync entities purged 0, local sync rows 0.
Receipt: .../d/erasure-receipts/90cc8cbbd16c69ba.json
Verify: aletheia erase verify .../d/erasure-receipts/90cc8cbbd16c69ba.json
ok .../d/erasure-receipts/90cc8cbbd16c69ba.json

The receipt lists counts and ids, never content. It carries an HMAC-SHA256 signature made with a local key in erasure.key, so erase verify proves the receipt was not edited. It does not prove that the data is gone from the disk.

Know these limits before you rely on it:

Dismiss or erase

DismissErase
Commandaletheia dismiss <id>aletheia erase --apply
Row removedNo, it is hiddenYes
ReversibleYes, aletheia restoreNo, except by importing the pre-erase export
ReceiptNoYes

Use dismiss for a memory that is stale or wrong. Use erase for something that must not stay. aletheia prune --apply deletes dismissed rows without a receipt.

Report a security problem

Use GitHub's private vulnerability reporting on the repository, as described in SECURITY.md. Do not open a public issue with the details.

Next steps