Security fixes ship in the latest 4.x release (4.2.0 at the time of writing). Older releases are not patched. Upgrade to the newest 4.x release before you report a problem that may already be fixed.
If you discover a security vulnerability in Vestige, please report it responsibly:
- DO NOT open a public GitHub issue
- Email the maintainer directly (see GitHub profile)
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
You can expect a response within 48 hours.
Vestige runs on your machine with your user permissions:
- Trusted: the MCP client or local agent that connects over stdio, and any other agent you run on the same machine. The first
vestige-mcpto start owns the store. Every later one attaches to it through an owner-only socket (mode0600) that needs a random token from an owner-only endpoint file, so any process that can read your data directory can use the store. - Untrusted: the content of tool arguments, which is validated before use, and every web page open in your browser.
HTTP and the dashboard are off unless you turn them on, and both answer only on 127.0.0.1. The dashboard checks Host and Origin on every request. A request that changes anything must come from the dashboard's own page or carry Authorization: Bearer <token>. The HTTP MCP transport takes the same token. Details: docs/CONFIGURATION.md.
- Make network requests in a default build. There is no embedding model to download and no startup version check.
vestige updatecontacts GitHub only when you run it.source_sync(--features connectors) andvestige sync --cloud(--features cloud-sync) are the only calls that use the network, and neither is in a 4.x release build. - Require telemetry, a hosted service or a cloud account
- Send telemetry or analytics
- Phone home to any server
- Read or write outside its data directory unless you point it there:
projectwrites only inside therootyou give it,codebasereads therepoPathyou pass, andvestige backupandmaintainexportwrite the folder you name
Everything is stored locally in the data directory. Strata, an append-only, hash-chained log, is the store. It sits in log/. Sealed segments carry a signed trailer, and writes are checked by the gate before they are admitted.
| Item | Location |
|---|---|
| Strata log | <data-dir>/log/ |
| Signing and identity keys | receipt-signing.key and actor.key in <data-dir>/. vestige backup does not copy them |
| HTTP and dashboard token | auth_token in the default per-user data directory, or VESTIGE_AUTH_TOKEN |
| Backups and exports | <data-dir>/backups/ and <data-dir>/exports/ |
On macOS the default data directory is ~/Library/Application Support/com.vestige.core/. Other platforms and overrides (--data-dir, VESTIGE_DATA_DIR) are in docs/STORAGE.md.
On Unix the data directory is 0700, and the lock, socket and endpoint files are 0600. Backups are owner-only (folders 0700, files 0600).
After an upgrade from v3 the old vestige.db and the owner-only backup the upgrade made stay in the data directory. They hold your pre-upgrade records. Delete them yourself when you no longer need a rollback.
The Strata log is not encrypted. Its hash chain and signed trailers prove integrity, not secrecy. Use OS-level encryption (FileVault, BitLocker, LUKS) and keep backups on encrypted storage. The SQLCipher option (encryption feature) belongs to the v3 SQLite engine and is not in any release build.
A Strata log cannot erase a record. suppress hides a record from every read and keeps its bytes. On Strata it cannot be undone. purge, memory actions purge and delete, and delete_knowledge return unavailable_in_4_0. If a secret reaches the log, rotate it. Real erasure is planned as crypto-erasure (#402).
Every write is scanned before it is admitted, in every stored field (content, type, tags, source and provenance) and for intentions too. A match refuses the write and names the kind of credential, never the value. Refused kinds: Google API keys, GitHub tokens, AWS access key ids, Slack tokens, PEM private keys, Azure client secrets, Anthropic keys, OpenAI keys and Stripe live keys. A bare password: ... line is not blocked. allowSecrets lets one save store a credential in its own text; the scope is always checked, and intentions are always gated. vestige scan-secrets reports anything already in the log. It opens the log directly, so run it while no Vestige server holds the store, or on a copy.
- Content is limited to 1,000,000 bytes per memory, and
itemsto 20 per call - Handles resolve exactly: an id, a unique id prefix, or an exact tag. There is no query language and no full-text index to inject into
- The dashboard, HTTP transport and attach socket refuse requests that fail their guards before any tool runs
projectrefuses a target that escapesroot, is a symlink, is not a regular file, or is larger than 2 MiBcodebaseingest_reporeads git locally with--no-show-signatureandcore.fsmonitor=false, so a hostile checkout's config cannot make it run a program
Dependencies are pinned in Cargo.lock. To see the current advisory status, run cargo audit against that lockfile. strata-store, strata-kernel and strata-verify set #![forbid(unsafe_code)]. Other crates use unsafe for a few operating-system calls: fsync on macOS, a disk-space check in the upgrader, and glibc shims in the server.
Operator Lite is a separate layer: a stdlib-only Python hook that applies pattern rules to commands before they run, and writes its own hash-chained receipts. It blocks what its closed rule set recognizes and that is routed through hooked tools. It cannot see around hooks. Receipts are hash-chained digests, not signatures. See operator-lite/README.md.
- No hardcoded secrets
- The credential gate runs on every write
- Exact handles only, with no query language to inject into
- Local-only transports, with Host, Origin and token checks
- Owner-only files and folders on Unix