Claims, limits, and how to check both
A privacy promise is only worth what you can check. Here is every claim the app makes, the attacker each one is meant to stop, and — with equal weight — where the protection ends.
No external audit
Everything below was designed, built and tested by one person. Treat the claims as carefully tested intentions until someone independent has looked (L-09).
Closed source, open to inspection
The source isn’t distributed, but the license explicitly lets you decompile, instrument, fuzz and packet-capture the app — and publish what you find (L-17).
No recovery, by design
There is no back door for anyone, including you. Forget your passphrase or lose your only key and the vault cannot be opened (C-02).
Sixteen claims, each tied to evidence
In the project, every claim is enforced by a ledger: a claim with no evidence carrying it — a test, or for the dependency pins a verification script — fails the build. Each lists the adversary it is meant to hold against.
| Id | Claim | Holds against |
|---|---|---|
| C-01 | Everything is encrypted at rest. Messages, titles, personas, memories and settings are sealed with AES-256-GCM under a key derived from your factors. The vault file contains no readable content. | Anyone who gets the file: a thief, a repair shop, a forensic examiner, a cloud sync you never asked for. |
| C-02 | No master key, no recovery, no escrow. Your passphrase and/or hardware key are the only way in. Lose them and the vault is gone — for you, and for everyone else. | The author, anyone who compels the author, anyone who compromises a build. |
| C-03 | Refusals give nothing away. A wrong passphrase, a wrong key and a file that is not a vault all look the same to the app. Nothing in the refusal says how close a guess was. | Someone probing a file to learn whether a guess was close. |
| C-04 | Nothing it ships can reach the network. No assembly references a networking API, and no bundled native library — SQLite, Skia, HarfBuzz, llama.cpp and its backends — imports one. There is no telemetry and no update check. | Telemetry, update checks, exfiltration by a compromised dependency. |
| C-05 | The only file outside a vault is a crash log. It holds an exception type and a stack scrubbed of file paths, your account name, and anything long enough to be diary content. | A forensic examiner reading what the app left on the host. |
| C-06 | A vault is exactly one file. No journals, no -wal/-shm sidecars, no temp copies. Lock it and it can be moved immediately. | Leaving fragments behind on a shared or borrowed machine. |
| C-07 | Locking zeroes the key. Key material is overwritten where it lived, not merely dereferenced. | Someone who dumps process memory after the vault is locked. |
| C-08 | Records are bound to their rows. Ciphertext that is moved, copied or swapped between rows fails authentication instead of being read as if it belonged there. | An intruder rewriting a diary using its owner’s own ciphertext. |
| C-09 | Integrity checks cover everything. The integrity check authenticates every encrypted record, finding them by asking the container rather than trusting a hand-kept list. | Silent bit rot, tampering, and a checker that drifts out of date. |
| C-10 | Hostile files can’t hurt the app. A damaged or malicious vault cannot crash or hang the app, exhaust memory, inject SQL, or produce unauthenticated content. | A vault from somewhere untrusted — a shared drive, an attachment, a restored backup. |
| C-11 | Backups are verified before they count. A backup is a full vault with every property above, and it is read back and verified before it can replace anything. | A backup that looks fine and isn’t, discovered on the day you need it. |
| C-12 | No downgrade path. An older vault is upgraded on first open and never run under older, weaker rules — and editing its header can’t talk the app into it. | An attacker downgrading a vault to a weaker format. |
| C-13 | Dependencies are pinned and audited. Every dependency, transitive ones included, is pinned by version and content hash, and audited for known vulnerabilities on every verified build. | A library that changes underneath a build, or a known-vulnerable one shipping unnoticed. |
| C-14 | Record sizes are blurred. Every record is padded to a 256-byte bucket before sealing. Every message from 1 to about 160 characters is stored at exactly the same size. | Reading the shape of a diary out of a file you can’t decrypt. |
| C-15 | Changing how a vault opens is safe. Changing unlock factors requires the current ones, and every new slot is proven to open the vault before the old arrangement is replaced. A missing enrolled key fails the whole change. | Someone who finds your vault unlocked and makes themselves the only way back in. |
| C-16 | Memories stay in their conversation. A memory is recalled only in the conversation it came from unless you mark it for all conversations. One setting, off by default, lifts that across the diary. | The private conversation surfacing in the everyday one, in front of whoever is watching the screen. |
What it does not protect you from
These aren’t fine print. Each is the consequence of a deliberate decision, written down so you can decide whether the trade is right for you.
| L-01 | The file’s size, record counts and modification time are visible without a key. |
| L-02 | The header is plaintext by design: format version, cipher and KDF parameters, slot types, and enrolled key ids. None of it opens anything. |
| L-03 | While the app runs, decrypted text lives in ordinary memory and can outlast locking — it can reach the page file, hibernation image or an OS crash dump. |
| L-04 | Passphrases pass through .NET strings, which cannot be zeroed. |
| L-05 | Replacing the whole vault with a genuine older copy of itself cannot be detected. |
| L-06 | Model (GGUF) files stay wherever you put them, unencrypted. Only the path lives in the vault. |
| L-07 | No hidden volume and no deniability. A vault is recognizable as an encrypted file. |
| L-08 | The bundled native libraries are third-party code: their imports are scanned, their behavior is not audited. |
| L-09 | No external security audit has been performed. |
| L-10 | Nothing here defends against a compromised OS, a keylogger, screen capture, malware running as you, or someone looking over your shoulder. |
| L-11 | The bundled .NET runtime contains socket code, as every general-purpose runtime does. The app provably never references it. |
| L-12 | A vault is an ordinary SQLite database: tools can see its table names, row counts and record sizes — never its content. |
| L-13 | Deleting an entry overwrites it in the vault file, but can’t reach a fact the AI already distilled from it, copies of the file made earlier, or encrypted leftovers of the app’s write journal on your disk. |
| L-14 | Embedding vectors are a lossy but partly invertible copy of your text. They are encrypted like the text and never exported. |
| L-15 | Keeping a memory in its conversation governs what the AI is shown, not how the vault is partitioned. The memory manager lists everything. |
| L-16 | A very long memory is semantically indexed only for its first ~3,000 tokens. It is stored in full, but may be condensed or shortened to fit when it is recalled. |
| L-17 | The source is not published, so test-backed claims rest on the author’s own test runs. The black-box checks below don’t. |
Worth doing if the file’s shape matters to you: keep your vault inside an encrypted container that is mounted only while you use it — a VeraCrypt volume or a BitLocker-encrypted VHDX. Whole-disk encryption alone doesn’t hide the vault from programs running while you’re logged in. Full-disk encryption does, however, cover the page file and hibernation image (L-03).
Verify it yourself
These checks treat the app as a black box. None of them needs the source code, and none asks you to trust anyone.
Your words aren’t in the file
Create a vault, write a distinctive phrase, lock it, and search the raw bytes. Expect nothing.
Select-String -Path .\diary -Pattern "the phrase you wrote" -Encoding utf8
Open it with sqlite3: you’ll see tables and a plaintext header describing how it is locked. Every content column is an opaque blob.
sqlite3 diary "SELECT id, length(data_enc) FROM messages;"
It never touches the network
Windows: block it outbound, use it normally — load a model, chat, back up, lock — then check the firewall log. There are no blocked attempts, because there are no attempts.
New-NetFirewallRule -DisplayName "MTS verification" -Direction Outbound `
-Program "C:\Program Files\My Thought Safe\MyThoughtSafe.exe" -Action Block
Linux: run it in a network namespace with no interfaces at all.
sudo unshare -n ./MyThoughtSafe-0.10.0-x86_64.AppImage
Record sizes are blurred
Write several short messages of very different lengths, then group by stored size. They collapse onto a single row.
sqlite3 diary "SELECT length(data_enc), count(*) FROM messages GROUP BY 1;"
Subtract 28 bytes of nonce and tag from any stored length: what remains is a multiple of 256.
Changing factors really changes them
In Settings → Security, change a test vault from a passphrase to a passphrase and a security key. Lock it, then try the passphrase alone (refused), the key alone (refused), and both (opens).
Try the change with a wrong current passphrase: it must refuse, and the vault must open exactly as before.
Nothing is left behind
Run the portable build from a USB stick, use it, close it. %LOCALAPPDATA%\MyThoughtSafe (or ~/.local/share/MyThoughtSafe) should not exist unless it crashed — and then only crash.log, with paths and your name replaced by <redacted>. No registry keys, no recent-files list, no cache.
You have the published binary
Compare your download’s SHA-256 against the published checksums. A match says the file arrived exactly as published. It says nothing about what’s inside — which is why the license lets you open it up.
Get-FileHash .\MyThoughtSafe-0.10.0-setup.exe -Algorithm SHA256
Found something? That is the most valuable thing anyone can contribute to this project. The license explicitly permits publishing it, unflattering or not.