Your Agent's Memory Is a Secret Store. Ours Now Redacts Before Persisting
Uteke's write path now detects keys, tokens and PEM blocks before they reach storage, masks them by default, and rejects the write in strict mode. CLI, server and MCP all report it.
Paste a log into your agent's memory and the paste is permanent. Session transcripts, CLI output, and .env dumps routinely carry live credentials, and most memory tools index exactly what you give them. One careless save and the key sits in storage, gets embedded, and resurfaces in recall results for months.
The OpenCode breach on October 17 made the stakes concrete. Provider credentials for a small slice of console users leaked, the recommended fix was "rotate your keys", and the Hacker News thread passed 900 points. The lesson generalizes: whatever a tool holds, it can leak. "Don't paste secrets" is a convention, and conventions fail on the day someone is tired.
Uteke, our local-first memory engine for AI agents, stopped relying on convention this week. Every write now passes a privacy filter before anything reaches storage. It ships in three modes: redact (the default, mask the secret and keep the write), strict (reject the write instead), and off. Detection combines fixed token rules, PEM private key block matching, and an assignment scanner that reads KEY= and TOKEN= style lines with entropy scoring for generic high-entropy values. When it redacts, the value is masked while the line's shape survives, the memory gets metadata.redacted, and an audit entry records what happened.
Reporting, so silence never hides a change
A filter that silently edits your data is its own failure mode. So the filter talks. uteke remember prints which secret kinds were masked, and its --json output gains a redacted field. uteke import reports masked and rejected row counts. Over MCP, uteke_remember states what was masked in its result. Strict mode failures are explicit on every surface: the HTTP API answers 400 secret_detected, and MCP writes fail with a secret_detected: tool error that names the kinds it found and never echoes the value.
The coverage gap that mattered most
Secrets hide in more places than body text. The first pass covered memory content; the follow-up extended the filter to tags, every string inside metadata, and the source field, across remember, update, import, room remember, and room consolidation. Before that change, a key tucked into a tag would have sailed through untouched, indexed like any other word.
One exception is deliberate and documented: restoring a structural export (export --full) is not filtered, so a backup comes back exactly as it left. Filter a restore and you corrupt the backup; the honest alternative is a clear note in the docs, and that is what shipped.
Test vectors instead of regex theater
Content filters fail in a specific way: loud false negatives and quiet false positives. The repo now publishes shared test vectors for the OSS filter (privacy-filter-cases.json), and they carry the scars of two adversarial reviews. Generic words such as key, pass, and token are no longer treated as names unless they appear in compounds. Quoted values, URL query strings, and glued pairs are read as whole units instead of fragments. The vectors exist so Uteke Cloud can pin filter parity in CI, rather than discovering drift through a support ticket.
Why the write path is the right place
Filtering before persistence also keeps the secret out of everything downstream. Uteke embeds content into a vector index and an FTS5 table at write time; a secret that never gets written never gets embedded, indexed, or returned. Read-time filtering cannot make the same claim, because the damage is already stored by then. Storage is also the one layer every path funnels through, which makes it the only place a single check covers all of them.
A memory system is a secret store unless you design against it. Agents get pasted logs, stack traces, config files, and shell output, and a memory server wired into an agent receives all of it. Saturday's breach came from server-side holding of provider keys; pasted secrets in memory content are the quieter sibling of the same problem, and the storage layer is where that half gets fixed.
Status, stated plainly
Everything above merged on the develop branch between October 20 and 21, in PRs #1503, #1510, and #1511, tracked under issue #1464. The latest tagged release is v0.21.1 from October 15, so if you install releases, the filter arrives in the next cut. Build from develop if you need it today. We would rather describe the merge state than imply a release that has not happened.
Configuration is one block: set [privacy] mode = "redact" in uteke.toml, or export UTEKE_PRIVACY_MODE (the docs cover the full resolution order). For context on the surrounding surface, the v0.21.0 release notes cover the MCP read-only policy and signed releases, and the Hermes integration guide walks through wiring Uteke into an agent. The product page is at codecora.dev/uteke.
Sources: issue #1464, the merged PRs (#1503, #1510, #1511), and the OpenCode breach thread.