Skip to content

fix(redact): mask credential values in harvest and synthesize saves - #114

Merged
raphasouthall merged 1 commit into
mainfrom
fix/113-redact-unattended-writes
Aug 24, 2026
Merged

fix(redact): mask credential values in harvest and synthesize saves#114
raphasouthall merged 1 commit into
mainfrom
fix/113-redact-unattended-writes

Conversation

@raphasouthall

Copy link
Copy Markdown
Owner

Closes #113.

What

Credential values no longer reach the two unattended memory write paths.

  • New neurostack.redact.redact_secrets(text) -> (text, kinds). Masks Google/Firebase API keys, nsk- keys, Stripe secret and restricted keys, GitHub, Slack, OpenAI, JWTs, PEM private-key blocks, Bearer tokens, AWS access keys, and password= assignments. Prefixes are kept (AIza***REDACTED***) so the text still says which kind of credential was removed.
  • harvest_sessions redacts the summary before the dedup check, so the stored form and the deduped form are the same string.
  • synthesize_observations redacts the generated learning before save_memory — a learning inherits its members' text, which is exactly how the two live leaks propagated.
  • Both report what fired (record["redacted"] / plan["redacted"]).

save_memory is deliberately NOT touched. Agent-written memories are intentional; silently rewriting them would corrupt real content. Mirrors the harvest-only TTL precedent from #36.

Precision over recall

False positives here permanently damage stored knowledge, so every pattern matches the shape of a value, not a mention. Cases taken verbatim from the live store that MUST survive:

  • No Firebase API key in the build at all.
  • apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  • The commit reverted to hardcoding a password, an unnecessary regression.
  • AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE (AWS's documented example, allowlisted)
  • DB_PASSWORD=${OPERATIONS_APP_PASSWORD} (env reference, not a value)

An already-masked string is a no-op: the marker contains no character any pattern accepts.

Live data cleaned

Sweep of /root/.local/share/neurostack/neurostack.db on LXC 122 found two real secrets across five rows: a Firebase web key in memories 98, 163 and learning 1841, and a full nsk- key in memory 217 and learning 1830. All masked in place with a ***REDACTED*** marker, then neurostack backfill memories re-embedded all five from the masked content. Post-check: 0 hits in memories, memories_archive, and memories_fts.

Gate

ruff check src/ tests/ clean. pytest -q: 791 passed (778 + 13 new in tests/test_redact.py, plus one added for the env-reference guard found in self-review). LSP diagnostics clean on all three touched files.

A sweep of the live store found a Firebase web API key in two harvested
observations and an nsk- key in a third, both inherited by synthesized
learnings. All five rows were masked in place and re-embedded; this stops
the next one from being written.

redact_secrets() matches the shape of a VALUE, not a mention: the sweep's
fifteen 'password' hits were all prose, and one was the AWS documentation
example key. Harvest redacts the summary before the dedup check so the
stored and deduped strings match; synthesize redacts the learning before
save. save_memory is deliberately untouched - agent-written memories are
intentional, and rewriting them would corrupt real content. Mirrors the
harvest-only TTL precedent from #36.

Part of #113
@raphasouthall
raphasouthall merged commit 11653ac into main Aug 24, 2026
5 checks passed
@raphasouthall
raphasouthall deleted the fix/113-redact-unattended-writes branch August 24, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Redact credential patterns in unattended memory writes (harvest + synthesize)

1 participant