Releases: saiyam1814/memwarden
Release list
v0.1.1 — firewall bypass in the explain path
Three defects found by running 0.1.0 on a real machine rather than in CI. The first is a firewall bypass, which is why this went out the same day.
Security: why --json returned refused content
The human path withheld a refused memory's title and narrative unless --content was passed. The JSON path returned before that policy was applied. Reproduced on a live 0.1.0 daemon:
$ memwarden why <stale-id> # correct
title (withheld — this memory is refused under the current policy)
$ memwarden why <stale-id> --json # leaked
title : "Read README.md"
narrative: "Read README.md. {\"success\":true,\"output\":\"<file-view path=..."
Anyone holding a refused observation id — a script, or a model told only the id — could recover firewalled content through the explain path. That contradicts the guarantee the product rests on.
Withholding is now enforced inside mem::why, before the response leaves core, so the raw HTTP endpoint is fixed and not merely the CLI. Requesting content requires an explicit boolean (include_content / --content) and returns it in a dedicated field wrapped in untrusted-data markers, rather than as bare title/narrative fields a caller would slurp by accident. (#80, closes #77)
status called every served memory "verified"
Under the default balanced policy the firewall serves verified, cosmetic, sourced and unsourced memories — each labeled — but the recorder stored a single injected total and status rendered it as N verified served. It now counts unique memories actually returned after policy filtering and token-budget packing, split by trust state.
Counts recorded before this release cannot be re-derived, so they are shown as legacy/unclassified rather than retroactively claimed as verified. (#81, closes #78)
The daemon log was world-readable and unbounded
The detached spawn path opened and chmod'd the log to 0600, but the launchd installer only pointed StandardOutPath/StandardErrorPath at the file — so launchd created it with the default 0644, measured at 3.7 MB world-readable on a real install. Logging is now descriptor-safe across both paths with regular-file, ownership, hard-link, symlink and containment checks, enforced 0600, and bounded to one 1 MiB current file plus one 1 MiB tail generation while preserving launchd's open inode. systemd continues to use journald. (#82, closes #79)
Upgrade
npm install -g memwarden@0.1.1
memwarden down && memwarden up854 tests · firewall eval 10/10 gates · packed-artifact smoke on Linux, macOS and Windows · published with SLSA provenance.
v0.1.0 — first public beta
The first public beta. Everything here was found by running the tool against a real install rather than a fixture, which is why the version moved off 0.0.x.
Fixed
- Canon push/pull actually round-trips — authenticated, project-scoped export/import preserving hashes, identity, content and attestation with safe merge semantics (#65)
- Explicit saves survive retention — a durable
mem::rememberpath with honest authorship, supersession, deletion receipts, and path normalization hardened against traversal, null bytes and symlink escapes (#66) - MCP search is firewalled and labeled — trust classification runs independently of the inclusion policy; search defaults to project-scoped current recall (#67)
- Consolidation preserves distinct same-file claims instead of folding two different decisions about one file into one memory (#68)
- Project identity is separate from checkout path, so worktrees of one repo stop confusing scoping across search, doctor, why, consolidation, Canon, bundles and fleet (#69)
The statistic we published was wrong
The half-life study's quantile helper took the upper-middle value for even samples instead of averaging the two middle ones, biasing every median high. The 180-day median is 61.8%, not the previously published 78.1%.
Worse, the bug masked the sensitivity analysis. fastapi's 180-day rate is 11.8% with localized docs excluded and 71.5% with them included — but both orderings placed 78.1% at the same index, so the two runs looked identical and that coincidence was reported as evidence of robustness. It was not. Fixed with tested R-7 quantiles plus a pinned four-repository manifest, both control arms, microdata and hashes, so anyone can recompute it (#70).
Added
- The release is gated on the packed npm artifact, not the repo. The exact
npm packoutput is installed under an isolated HOME and driven through real CLI, daemon, MCP, authenticated HTTP, persistence, Canon, erasure and clean shutdown — on Linux, macOS and Windows. This is precisely the check that would have caught the 0.0.5 defect whereconsolidate.jswas simply absent from the published tarball (#71). - A
cosmetictrust state. A reformatted file whose normalized content is unchanged is labeledsource-cosmetic— current, but explicitly not byte-identical — and is admitted underverified-only. Two eval gates bound it:cosmetic-admitted(reformatted memory flows under strict policy) andcosmetic-bounded(genuinely drifted memory is still refused). The pair is the point; alone, the first would let "cosmetic is allowed" drift into "any change is allowed" without the eval noticing.
Install
npm install -g memwarden
memwarden up
memwarden status # daemon, firewall activity, storage
memwarden doctor . # trust audit of this projectMemories captured before 0.0.8 keep their older shape and age out through normal retention. npm run inspect (from a clone) grades your store; memwarden doctor . --fix-stale clears stale records now.
824 tests · firewall eval 10/10 gates · packed-artifact smoke on Linux, macOS and Windows · published with SLSA provenance.
v0.0.9 — JSON envelopes out of memory bodies, plus store inspection
Found by running the new npm run inspect on a live brain within an hour of publishing 0.0.8, which is the whole argument for the command existing.
The defect 0.0.8 missed
0.0.8 fixed titles and facts but still appended raw tool output verbatim:
Wrote inspect-store.ts. {"type":"create","filePath":"…","content":"//\n…"
An entire written file, stored inside its own memory. Output is now mined for the one field a human would read (stdout, output, stderr, message) and everything else is dropped. content/file-body fields are deliberately never harvested — they are the payload we are trying not to keep.
New: npm run inspect
Grades the live brain against the same rules the capture path enforces — bare-tool-name titles, JSON in bodies, records with no facts and no concepts, whether importance has any spread — then prints the most recent captures to read, because no aggregate tells you whether the knowledge is worth having.
It exists because 761 passing tests and a 100% firewall eval both reported "healthy" while the store was full of tool logs. Reading the rows is the only check that catches this class of defect. Reads a snapshot, never the live file; mutates nothing.
Two bugs the inspector found in itself
- Its JSON check only looked at the start of a body, so
Wrote foo.ts. {…}read as clean. - Its bare-tool-name check was case-sensitive while hosts disagree on casing (
Readvsread).
Upgrading
npm install -g memwarden@0.0.9
memwarden down && memwarden up
npm run inspect # from a clone, to grade your own storeRecords captured before 0.0.8 keep their old shape and age out through retention.
763 tests · firewall eval 8/8 · published with SLSA provenance.
v0.0.8 — memories describe the change, not the tool
One fix, and it is the one that decides whether any of the rest matters.
The defect (closes #3)
Inspecting a real six-week 0.0.7 install found 379 stored memories shaped like this:
title: Write
body : {"file_path":"/Users/…/email-to-preet.txt","content":"Subject: Re: …
facts: [] concepts: []
Every title was one of six tool names, so nothing was rankable. Every body was raw tool-input JSON, so nothing was readable. facts and concepts were hardcoded empty, so hybrid search had almost no real terms to match on.
Provenance and hashing worked perfectly — they were verifying junk.
The fix
Rule-based extraction, still zero-LLM and still no token spend:
| Before | After |
|---|---|
Write |
auth.ts: ROTATE_MS = 900_000 → ROTATE_MS = 3_600_000 |
Bash |
npm test -- --coverage |
Grep |
Searched "authentication" |
{"file_path":…} as body |
prose; raw tool input never stored as content |
facts: [] |
changed: X → Y, ran: …, error: … |
concepts: [] |
api, triggers, MAX_RETRIES |
A bare read with nothing extractable now sinks below the retention floor, so the sweep ages it out instead of distilling it into a permanent row.
Five more defects, found by inspecting live captures rather than fixtures
Each is pinned by a test:
- Loose error matching fired on any file whose content mentioned an error, and on
{"success":true}envelopes — marking every capture importance 6 and destroying ranking. - Raw JSON leaked back into facts through error-line extraction.
- The OS username leaked into concepts from
/Users/<name>/…on every memory — personal data, zero retrieval value. - Escape sequences fused into identifiers (
\tisPremium→tisPremium). - Shouty English (
THE,GATE,PASS) matched CONSTANT_CASE and buried real identifiers.
The uncomfortable part
Three pre-existing tests were asserting the old broken behavior (expect(title).toBe("Grep")). The suite was green because it was protecting the defect — which is why this survived six weeks of use. There is now a regression gate that fails if any title is a bare tool name or any body parses as JSON carrying a file_path.
Existing installs
Memories captured before this release keep their old shape. They age out through normal retention, or memwarden doctor . --fix-stale clears the stale ones now. New captures are correct immediately after upgrading.
npm install -g memwarden@0.0.8
memwarden down && memwarden up761 tests · firewall eval 8/8 · published with SLSA provenance.
v0.0.6 — first public beta
The first public beta. Two of these are corrections to bugs that made the layer dishonest in practice, and they are listed first on purpose.
Fixed
The durability contract: code-backed knowledge is distilled, never dropped.
The retention sweep deleted expiring observations without checking whether anything durable had been distilled from them first. Measured on a real 0.0.5 install: 15,771 observations captured, 0 memories, and hundreds of code-backed rows removed per hour at the TTL — the layer was a sieve.
At the TTL an observation carrying file provenance is now promoted into a Memory (via distillMembers, the same primitive the consolidate pipeline uses), carrying its content and capture-time hashes verbatim so Verified Recall still re-checks it against the live file. Repeat touches of a file converge on one memory id, so storage still shrinks. Observations with no provenance have nothing durable to promote and are deleted as before. This also makes sweep-vs-consolidate ordering irrelevant. MEMWARDEN_FORGET_PROMOTE=off restores the old behavior.
Consolidation reaches published builds. The distillation pipeline was absent from the published 0.0.5 tarball, which is why installs reported 0 memories.
status diagnoses itself. It printed 0 memories beside 15771 observations for weeks without flagging it. It now names the condition and what to run, and prints the on-disk footprint with a reclaim hint.
Added
Verified Memory Canon — git-native portable verified memory.
memwarden canon push promotes distilled memories into .memwarden/canon.jsonl (one JSON record per line, repo-relative paths, capture-time SHA-256 per file). canon verify re-hashes the canon against any checkout and reports UNCHANGED / REFORMATTED / DRIFTED / UNVERIFIABLE with the files that drifted; --strict exits 1 as a CI gate. canon pull loads what still holds into the local brain. canon reanchor re-hashes drifted records against the current checkout and records who asserted they still hold (attestation, reported separately from capture-time proof).
Trust becomes portable with no server, no account, and no vendor. Hardened after adversarial review: a secret gate with no override (--all relaxes staleness only, because git history is a one-way door), pull refuses unverifiable records and requires --yes, drift severity so --strict never fails on reformatting, and honest labels that state the coverage limit. action.yml ships the PR gate as a composite GitHub Action.
Fleet mode, phase 2: an agent registry and memwarden fleet status [--cwd dir] [--json] with POST /memwarden/fleet/status. Thanks @sivasubramanian95 (#34, #25) and #35 (#26).
memwarden compact --prune-history [--keep-days N]: the oplog held 316MB of payloads against 16.6MB of live state — 95% of the database was superseded copies. Pruning drops those while keeping every entry's payload_hash, so the chain still verifies. Measured on a real install: 438MB → 203MB, 222MB reclaimed, and verify returns verified: true across all 193,298 entries.
Changed
up/down are scoped to the daemon they target: pointing MEMWARDEN_URL / MEMWARDEN_REST_PORT at a throwaway daemon no longer rewrites every real tool's config or unloads the user-global service. URL comparison is normalized, and non-default down --data refuses to delete the default brain. Thanks @d-cryptic (#21, #17).
Verification
tsc --noEmit clean · 729 tests · firewall eval 8/8 at 100% · canon and compact both verified live against a real daemon.
Note
npm publish for this tag failed on registry auth (the NPM_TOKEN secret needs refreshing or Trusted Publishing needs configuring). Install from source until 0.0.6 appears on npm.