Skip to content

feat(memory-security): prevent secret leakage into agent memory with redaction, validation, and diagnostics#1224

Merged
senamakel merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/prevent-secrets-from-memory-leak
May 5, 2026
Merged

feat(memory-security): prevent secret leakage into agent memory with redaction, validation, and diagnostics#1224
senamakel merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/prevent-secrets-from-memory-leak

Conversation

@YellowSnnowmann
Copy link
Copy Markdown
Contributor

@YellowSnnowmann YellowSnnowmann commented May 5, 2026

Summary

  • Added centralized memory safety layer to detect and sanitize secret-like values before persistence

  • Hardened memory persistence boundaries:

    • memory_docs
    • kv_global
    • kv_namespace
    • episodic_log
    • Secret-bearing payloads are redacted
    • Unsafe identifiers are rejected
  • Added validation in memory_store tool:

    • Blocks secret-like memory writes early
  • Expanded redaction coverage:

    • OAuth artifacts
    • Cloud keys
    • Provider tokens
    • Private-key blocks
    • Auth header formats
  • Added [memory:safety] diagnostics:

    • Makes redaction/rejection observable during debugging
  • Updated memory-store documentation to reflect new security behavior


Problem

  • Agent memory may receive sensitive data:

    • Access tokens
    • Credentials from tool/user flows
  • Previous behavior risked:

    • Long-lived persistence of secrets
    • Increased exposure risk
  • Protection gaps:

    • Inconsistent handling across:
      • document memory
      • KV storage
      • episodic memory

Solution

  • Introduced shared safety module:

    • src/openhuman/memory/safety/
  • Capabilities:

    • Pattern-based secret detection
    • Payload redaction (text + JSON)
    • Sensitive key masking for structured data

Enforcement at Memory Boundaries

Documents

  • Sanitize:
    • content
    • metadata
    • tags
  • Reject secret-like namespace/key identifiers

KV Storage

  • Sanitize values
  • Reject unsafe namespace/key identifiers

Episodic Memory

  • Sanitize:
    • content
    • lessons
    • tool-call payloads
  • Reject unsafe session/role identifiers

Tool-Level Guard

  • memory_store:
    • Rejects secret-like content before write attempts

Design Tradeoffs

  • Identifiers

    • Rejected (not rewritten)
    • Avoids key-collision/regression risk
  • Payloads

    • Redacted (not rejected)
    • Preserves functionality while removing sensitive data

Submission Checklist

  • Tests

    • Added/updated per docs/TESTING-STRATEGY.md
    • Includes:
      • happy paths
      • failure/edge cases
  • Coverage

    • Diff coverage ≥ 80%
    • Tools:
      • pnpm test:coverage
      • pnpm test:rust
    • Enforced via:
      • .github/workflows/coverage.yml
  • Coverage Matrix

    • Updated in:
      • docs/TEST-COVERAGE-MATRIX.md
    • Or marked N/A if behavior-only change
  • Feature IDs

    • Listed under ## Related
  • Network Dependencies

    • No new external dependencies
    • Uses mock backend
  • Manual Smoke Checklist

    • Updated if release surface impacted:
      • docs/RELEASE-MANUAL-SMOKE.md
  • Linked Issues

    • Included via:
      • Closes #NNN

Impact

  • Runtime / Platform

    • Affects Rust core memory subsystem
    • Impacts desktop app + CLI memory persistence flows
  • Security

    • Strong improvement:
      • Secrets are redacted or rejected before storage
  • Compatibility

    • Behavior change:
      • Secret-like identifiers (namespace/key) now rejected
  • Performance

    • Minimal overhead:
      • Regex checks at write-time
    • No architectural changes

Related

Summary by CodeRabbit

  • New Features

    • Built-in secret detection and automatic redaction for documents, KV, and episodic memory to prevent storing credentials, tokens, and private keys.
    • Inputs that look like secrets are rejected with clear error messages and warnings; sanitized values are stored when possible.
    • Sanitization reports now include depth-based redaction counts to better surface nested JSON redactions.
  • Tests

    • Added integration and unit tests covering detection, redaction, rejection, and persistence behavior.
  • Documentation

    • Updated memory store docs to describe the safety helpers and emitted diagnostics.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e404d27f-cede-4ca4-8cbe-730353dc46ac

📥 Commits

Reviewing files that changed from the base of the PR and between d23e8fe and 7089cdb.

📒 Files selected for processing (5)
  • src/openhuman/memory/safety/mod.rs
  • src/openhuman/memory/store/unified/documents.rs
  • src/openhuman/memory/store/unified/fts5.rs
  • src/openhuman/memory/store/unified/kv.rs
  • src/openhuman/tools/impl/memory/store.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/tools/impl/memory/store.rs
  • src/openhuman/memory/store/unified/documents.rs

📝 Walkthrough

Walkthrough

Adds a new safety memory submodule for detecting, blocking, and redacting likely secrets; integrates it into document, KV, episodic write paths and a memory-store tool to reject or sanitize inputs before persisting. Includes tests and README updates.

Changes

Secret Detection & Sanitization

Layer / File(s) Summary
Data Shape / Exports
src/openhuman/memory/mod.rs, src/openhuman/memory/safety/mod.rs
Exports new safety submodule. Introduces SanitizationReport (adds depth_redactions) and Sanitized<T> wrapper.
Core Detection / Sanitizers
src/openhuman/memory/safety/mod.rs
Adds regex-based BLOCK_PATTERNS and REDACTION_PATTERNS, has_likely_secret(), sanitize_text(), sanitize_json() (with sanitize_json_inner, max-depth guard), sanitize_document_input(), is_sensitive_key(), constants (REDACTED_*, MAX_JSON_SANITIZE_DEPTH), and unit tests.
Document Write Integration
src/openhuman/memory/store/unified/documents.rs
Rejects secret-like namespace/key via has_likely_secret, runs sanitize_document_input, rebinds sanitized input, and logs warnings when sanitization changed payloads.
KV Write Integration
src/openhuman/memory/store/unified/kv.rs
Rejects secret-like keys/namespaces, sanitizes JSON via sanitize_json, logs on changes, and persists sanitized JSON string.
Episodic Write Integration
src/openhuman/memory/store/unified/fts5.rs
Rejects secret-like session_id/role, sanitizes content/lesson with sanitize_text, parses & sanitizes tool_calls_json (or falls back to text), aggregates report, logs warnings, and persists sanitized fields.
Tool-level Integration
src/openhuman/tools/impl/memory/store.rs
MemoryStoreTool::execute rejects secret-like content via has_likely_secret, logs warning and returns error instead of calling memory store; adds unit test.
Documentation
src/openhuman/memory/store/README.md, src/openhuman/memory/store/unified/README.md
Adds notes about safety helpers and [memory:safety] diagnostics for rewritten payloads.
Tests
src/openhuman/memory/store/unified/documents_tests.rs, src/openhuman/memory/store/unified/fts5.rs, src/openhuman/tools/impl/memory/store.rs
Adds integration/unit tests verifying redaction persistence, JSON redaction, rejection messages for secret-like keys/namespaces/session_ids, and max-depth behavior.

Sequence Diagram

sequenceDiagram
    actor User
    participant Tool as MemoryStoreTool
    participant Safety as Safety Module
    participant Writer as Memory Writer
    participant Storage as Persistent Storage

    User->>Tool: execute(content, namespace, key, ...)
    Tool->>Safety: has_likely_secret(content)?
    Safety-->>Tool: true
    Tool->>User: error("looks like a secret")
    Note over Storage: ✗ not persisted

    User->>Tool: execute(safe_content, namespace, key, ...)
    Tool->>Safety: has_likely_secret(content)?
    Safety-->>Tool: false
    Tool->>Writer: request store(...)
    Writer->>Safety: sanitize_document_input() / sanitize_json() / sanitize_text()
    Safety-->>Writer: Sanitized{ value, report }
    Writer->>Storage: persist(sanitized value)
    Storage-->>Writer: stored
    Writer-->>Tool: success
    Tool-->>User: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

🐰 I hop with regex in my paws,

I sniff the secrets, sound the cause.
I trim the keys, I hush the lines,
So memory sleeps with tidy spines.
Hooray — no tokens in our stores!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding memory security via secret redaction, validation, and diagnostics to prevent secret leakage into agent memory.
Linked Issues check ✅ Passed All coding requirements from issue #687 are met: end-to-end protections prevent secrets via redaction/validation across memory persistence boundaries [documents, KV, episodic], diagnostics enable observability, and comprehensive tests validate behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #687: new safety module, memory persistence layer hardening, redaction logic, validation checks, diagnostics, and related tests—no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 89.47% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@YellowSnnowmann YellowSnnowmann marked this pull request as ready for review May 5, 2026 12:44
@YellowSnnowmann YellowSnnowmann requested a review from a team May 5, 2026 12:44
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/openhuman/memory/store/unified/fts5.rs (1)

140-143: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid logging the full session_id after insert.

This still writes every accepted session identifier verbatim to logs. Even with the new secret detector, session_id is a high-risk correlation field and should be reduced to a count, hash, or other opaque handle.

Suggested fix
     tracing::debug!(
-        "[fts5] inserted episodic entry: session={}, role={}",
-        entry.session_id,
-        entry.role
+        "[fts5] inserted episodic entry: session_chars={} role={}",
+        entry.session_id.chars().count(),
+        entry.role
     );

As per coding guidelines, **/*.rs: “never log secrets or full PII”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/memory/store/unified/fts5.rs` around lines 140 - 143, The debug
log in fts5.rs is emitting the full session identifier; replace the verbatim
session_id usage in the tracing::debug call that references entry.session_id
(and still mention entry.role if needed) with an opaque value—e.g., log a
truncated/hashed form of entry.session_id or a session counter/handle instead—so
that the tracing::debug call no longer writes the full PII; update any code that
computes the placeholder (hash/truncate/lookup) next to the existing
tracing::debug invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/memory/safety/mod.rs`:
- Around line 303-323: is_sensitive_key currently only matches exact names and a
few suffixes, so keys like "db_password", "secret_key", or "api_secret" are
missed; update is_sensitive_key to check normalized (lowercased,
alphanumeric-only) keys for contains/ends_with patterns such as "password",
"secret", and "key" as well as the existing "token", "apikey", and
"clientsecret" checks (e.g., use normalized.contains("password") ||
normalized.ends_with("password") || normalized.contains("secret") ||
normalized.ends_with("secret") || normalized.ends_with("key") in addition to
existing matches) so common variants are masked; keep using the same normalized
variable and function name to locate and modify the logic.
- Around line 256-301: sanitize_json_inner currently recurses unbounded; add a
depth guard by changing sanitize_json_inner to accept a depth parameter (e.g.,
fn sanitize_json_inner(value: &Value, depth: usize) -> Sanitized<Value>) and
introduce a constant MAX_SANITIZE_DEPTH; on each recursive call pass depth + 1,
and if depth >= MAX_SANITIZE_DEPTH stop recursing and return a safe replacement
(e.g., Value::String(REDACTED_SECRET.to_string()) or a small sentinel) while
incrementing an appropriate counter on SanitizationReport (e.g.,
truncation/depth_redactions) so callers of sanitize_json_inner (and
sanitize_text if needed) are updated to call the new signature starting with
depth 0.

In `@src/openhuman/tools/impl/memory/store.rs`:
- Around line 95-104: The current safety rejection logs raw namespace and key
values (in the branch calling safety::has_likely_secret(content)), which can
leak secrets; change the branch so it never formats or logs namespace/key
directly—either check namespace and key with safety::has_likely_secret and
reject early, or log only non-sensitive metadata (e.g., content length and
masked identifiers or counts). Update the log::warn! call in the memory_store
safety branch to remove namespace and key from the message (use counts or masked
placeholders), and ensure the function still returns the same ToolResult::error
message without exposing identifiers.

---

Outside diff comments:
In `@src/openhuman/memory/store/unified/fts5.rs`:
- Around line 140-143: The debug log in fts5.rs is emitting the full session
identifier; replace the verbatim session_id usage in the tracing::debug call
that references entry.session_id (and still mention entry.role if needed) with
an opaque value—e.g., log a truncated/hashed form of entry.session_id or a
session counter/handle instead—so that the tracing::debug call no longer writes
the full PII; update any code that computes the placeholder
(hash/truncate/lookup) next to the existing tracing::debug invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da9491a1-d54e-408f-b38b-d2547f54fc3d

📥 Commits

Reviewing files that changed from the base of the PR and between e945390 and d23e8fe.

📒 Files selected for processing (9)
  • src/openhuman/memory/mod.rs
  • src/openhuman/memory/safety/mod.rs
  • src/openhuman/memory/store/README.md
  • src/openhuman/memory/store/unified/README.md
  • src/openhuman/memory/store/unified/documents.rs
  • src/openhuman/memory/store/unified/documents_tests.rs
  • src/openhuman/memory/store/unified/fts5.rs
  • src/openhuman/memory/store/unified/kv.rs
  • src/openhuman/tools/impl/memory/store.rs

Comment thread src/openhuman/memory/safety/mod.rs Outdated
Comment thread src/openhuman/memory/safety/mod.rs
Comment thread src/openhuman/tools/impl/memory/store.rs
@senamakel senamakel merged commit a3b2fb8 into tinyhumansai:main May 5, 2026
20 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
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.

Prevent secrets from leaking into agent memory

2 participants