Skip to content

fix(security): redact credentials before persistence in session capture (salvage #24758 + #19855) - #31758

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f9dd4507
May 25, 2026
Merged

fix(security): redact credentials before persistence in session capture (salvage #24758 + #19855)#31758
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f9dd4507

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Combined salvage of two overlapping PRs that both target credential leaks in persisted history.

Summary

Two-layer redaction at the persistence boundary so credentials never reach state.db, session_*.json, or compression input.

Threat

Models occasionally inline secrets into natural-language responses or tool arguments — a curl with Authorization: Bearer sk-..., a github PAT mentioned in an explanation, an API key pasted into user input. Without redaction at the persistence boundary, these end up in:

  • state.db (canonical message store)
  • session_*.json (debug snapshots users treat as shareable for support)
  • compression output (re-summarized into future turns)
  • gateway delivery (echoed back to users)

Changes

Layer 1 — earliest interception, before message enters history:

  • agent/chat_completion_helpers.py :: build_assistant_message
    • Redact assistant text content
    • Redact tool_call.function.arguments (tool execution uses the raw API response, not this dict, so the persisted shape is safe to redact)

Layer 2 — defense-in-depth at JSON snapshot time:

  • run_agent.py :: _save_session_log
    • New _redact_message_content() static helper that handles both string content and OpenAI/Anthropic multimodal list-of-parts shape (image parts pass through untouched, only text/content fields go through the redactor)
    • Applied to every message's content and the cached system prompt

Both layers respect HERMES_REDACT_SECRETS (via redact_sensitive_text) — no-op when disabled.

Test plan

4 new tests in TestSaveSessionLogRedactsSecrets:

  • api key in tool content
  • api key in user message
  • api key in cached system prompt
  • multimodal list-of-parts (image preserved, text redacted)

Tests use an autouse fixture forcing _REDACT_ENABLED=True because the hermetic conftest defaults the env var to false.

pytest tests/run_agent/test_run_agent.py -k RedactsSecrets  # 4 passed
pytest tests/run_agent/test_run_agent.py tests/agent/test_anthropic_adapter.py tests/agent/test_redact.py  # 578 passed

Salvage scope

Co-authored-by: vgocoder hua.zhong@kingsmith.com
Co-authored-by: liuhao1024 liuhao03@bilibili.com

Closes #24758
Closes #19855

vgocoder and others added 2 commits May 24, 2026 17:57
Two-layer redaction at the persistence boundary so credentials never reach
state.db, session_*.json, or compression:

1. agent/chat_completion_helpers.py :: build_assistant_message
   - Redact assistant content before the message dict is constructed
     (catches PATs / API keys the model inlines into natural language)
   - Redact tool_call.function.arguments at the same site (catches secrets
     inlined into tool args, e.g. terminal command=curl -H 'Authorization: ...')
   Tool execution uses the raw API response object, not this dict, so
   redacting the persisted shape is safe.

2. run_agent.py :: _save_session_log
   - Add _redact_message_content() static helper that handles both string
     content and OpenAI/Anthropic multimodal list-of-parts (image parts
     pass through untouched, only text/content fields are redacted)
   - Apply to every message + the cached system prompt before writing
     session_*.json

Both layers respect HERMES_REDACT_SECRETS via redact_sensitive_text —
no-op when disabled.

Tests (TestSaveSessionLogRedactsSecrets, 4 cases):
  - api key in tool content
  - api key in user message
  - api key in system prompt
  - multimodal list-of-parts (image part preserved, text redacted)
Tests use an autouse fixture to force _REDACT_ENABLED=True because the
hermetic conftest defaults the env var to false.

Salvaged from PR #24758 by @vgocoder (build_assistant_message + session_log)
+ PR #19855 by @liuhao1024 (multimodal list helper, system_prompt redaction).
Kept only the redaction concern from #19855; its unrelated whatsapp npm
timeout + PATCH_SCHEMA changes are out of scope and dropped.

Refs #19798 (PAT leak via assistant inline mention), #19845 (session capture
credential leak).

Co-authored-by: liuhao1024 <liuhao03@bilibili.com>
Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
@teknium1
teknium1 merged commit bf2f3b2 into main May 25, 2026
22 checks passed
@teknium1
teknium1 deleted the hermes/hermes-f9dd4507 branch May 25, 2026 00:58
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-f9dd4507 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9104 on HEAD, 9104 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4851 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants