hermes - ✅(Solved) Fix Session capture leaks API credentials in plaintext to session_*.json [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#19845Fetched 2026-05-05 06:04:47
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×3commented ×2cross-referenced ×1

Hermes Agent's session capture writes raw HTTP request/response payloads to ~/.hermes/sessions/session_*.json (and per-profile equivalents) without redaction. When those payloads include an Authorization header or an inline api_key field — which is the common case for any model provider call — the credential ends up in the session log in plaintext.

Workers logs at ~/.hermes/kanban/logs/<task>.log are correctly redaction-aware (verified clean). The issue appears to be specific to session-capture serialization, not generic logging.

Root Cause

auth.json and .env files holding credentials is expected — they're the credential store. Session JSONs holding credentials is not expected — they're conversation transcripts. Users reasonably treat sessions as something they could share (for debugging, support requests, replay), not as credential storage.

The exposure surface is:

  • Time Machine backups (often to non-encrypted external drives)
  • Cloud sync tools that include ~/.hermes/
  • Sharing a session for support/debugging
  • Any process running as the user that walks the home dir

If FileVault/full-disk-encryption is on, the at-rest exposure is bounded — but the in-flight exposure (sharing, backup, sync) is not.

Fix Action

Fixed

PR fix notes

PR #19855: fix(agent): redact credentials in session capture logs

Description (problem / solution / changelog)

Summary

Session capture writes raw messages to ~/.hermes/sessions/session_*.json without redacting credentials. When API calls include Authorization headers or inline api_key fields, the credentials end up in session logs in plaintext.

Users reasonably treat session files as shareable (debugging, support, replay), not as credential stores.

Root Cause

_save_session_log() in run_agent.py saves messages verbatim to the session JSON file. The existing redact_sensitive_text() function in agent/redact.py handles all credential patterns (API keys, Bearer tokens, JSON fields, etc.) but was never applied to session capture — only to worker logs and verbose output.

Fix

  1. Import redact_sensitive_text from agent.redact into run_agent.py
  2. Add _redact_message_content() helper that applies redaction to string content and recursively handles list-type content (multimodal messages with text/image parts)
  3. Apply redaction in _save_session_log() to every message's content and the system prompt field before writing to the session JSON file

The redaction respects the existing HERMES_REDACT_SECRETS config toggle — when disabled, content passes through unchanged.

Regression Coverage

5 new tests in TestSaveSessionLogRedactsSecrets:

  • test_redacts_api_key_in_message_content — Bearer token in tool response
  • test_redacts_api_key_in_user_message — sk-ant-* key in user text
  • test_redacts_system_prompt_credentials — API key in cached system prompt
  • test_redacts_list_type_content — multimodal content with text + image parts
  • test_no_redaction_when_disabled — respects HERMES_REDACT_SECRETS=0

All 305 existing tests in test_run_agent.py continue to pass.

Testing

~/.hermes/workdir/hermes-agent/.venv/bin/python -m pytest tests/run_agent/test_run_agent.py -x -q
# 305 passed

~/.hermes/workdir/hermes-agent/.venv/bin/python -m pytest tests/agent/test_redact.py -x -q
# 75 passed

Fixes Session capture leaks API credentials in plaintext to session_*.json #19845

Changed files

  • gateway/platforms/whatsapp.py (modified, +4/-1)
  • run_agent.py (modified, +27/-2)
  • tests/run_agent/test_run_agent.py (modified, +88/-0)
  • tests/tools/test_file_tools.py (modified, +37/-0)
  • tools/file_tools.py (modified, +6/-6)

Code Example

grep -l "<credential prefix>" ~/.hermes/sessions/session_*.json
RAW_BUFFERClick to expand / collapse

Summary

Hermes Agent's session capture writes raw HTTP request/response payloads to ~/.hermes/sessions/session_*.json (and per-profile equivalents) without redaction. When those payloads include an Authorization header or an inline api_key field — which is the common case for any model provider call — the credential ends up in the session log in plaintext.

Workers logs at ~/.hermes/kanban/logs/<task>.log are correctly redaction-aware (verified clean). The issue appears to be specific to session-capture serialization, not generic logging.

Reproduction

On a freshly-installed Hermes (v0.12.0 / current main), have a profile route to any provider that uses an Authorization: Bearer <key> header (Ollama Cloud, OpenRouter, Anthropic, etc.). Send any prompt. Then:

grep -l "<credential prefix>" ~/.hermes/sessions/session_*.json

For Ollama Cloud keys (the <32-hex>.<base64> format), this returns one or more session files for nearly every chat that hit the provider.

Evidence

After scanning ~450 session files in ~/.hermes/sessions/ + per-profile sessions/ dirs with regex patterns for known credential formats:

Credential patternApprox leak rate
Ollama Cloud (<hex>.<b64>)~7% of sessions
OpenRouter (sk-or-v1-...)~1% of sessions
Bearer header (Bearer ...)~2% of sessions
Google (AIza...)<1% of sessions

Worker logs at ~/.hermes/kanban/logs/*.log scanned with the same patterns: 0 leaks. So redaction logic exists somewhere in the codebase; it just isn't applied to session capture.

Why this matters

auth.json and .env files holding credentials is expected — they're the credential store. Session JSONs holding credentials is not expected — they're conversation transcripts. Users reasonably treat sessions as something they could share (for debugging, support requests, replay), not as credential storage.

The exposure surface is:

  • Time Machine backups (often to non-encrypted external drives)
  • Cloud sync tools that include ~/.hermes/
  • Sharing a session for support/debugging
  • Any process running as the user that walks the home dir

If FileVault/full-disk-encryption is on, the at-rest exposure is bounded — but the in-flight exposure (sharing, backup, sync) is not.

Suggested fixes

Listed roughly in order of mechanical simplicity:

  1. Redact at capture time. When writing to session_*.json, run the request/response payloads through a regex pass that replaces matching credential patterns (sk-proj-..., sk-or-v1-..., AIza..., gsk_..., <hex>.<b64> Ollama format, Bearer ...) with <REDACTED> or <REDACTED:OPENAI> etc. The redaction layer evidently exists for worker logs; lifting it into session capture is the cheap path.

  2. Redact at the HTTP-client layer. Strip Authorization headers from request objects before they're serialized into session capture. This is one place to fix and catches the broadest class of credential leaks (everything header-based).

  3. Configurable redaction policy. Add a config option (sessions.redact_credentials: true|false|patterns_list) defaulting to true. Lets paranoid users add custom patterns without recompiling.

  4. Migration path for existing sessions. Optional hermes sessions redact CLI subcommand that walks existing session files and applies the same regex to clean up historical leaks.

A combination of (2) + (4) probably gives the best protection-per-line-of-code: stop new leaks at the HTTP layer, offer a one-shot to clean history.

Environment

  • Hermes Agent v0.12.0 (2026.4.30) — current main, 232 commits past the v0.12.0 tag
  • macOS 25.4.0 (Darwin)
  • Python 3.11.14
  • 7 active profiles (admin, coding-focused, gateway candidate, local-model testing, etc.)
  • Reproducible across multiple providers (Ollama Cloud, OpenRouter, Z.AI, Google) — not provider-specific

extent analysis

TL;DR

Redact sensitive credentials from session capture by modifying the HTTP-client layer to strip Authorization headers or implementing a regex-based redaction at capture time.

Guidance

  • Identify the source of credential leaks in session capture and prioritize fixing the HTTP-client layer to prevent future leaks.
  • Consider implementing a configurable redaction policy to allow users to customize redaction patterns.
  • Develop a migration path to redact existing sessions, such as a CLI subcommand to clean up historical leaks.
  • Verify the effectiveness of the fix by scanning session files for credential patterns and checking worker logs for redaction awareness.

Example

No code snippet is provided as the issue does not imply a specific code change, but rather a design or implementation decision.

Notes

The solution may require modifications to the Hermes Agent's session capture mechanism and HTTP-client layer. The effectiveness of the fix depends on the implementation details, which are not provided in the issue.

Recommendation

Apply workaround by redacting credentials at the HTTP-client layer, as it provides the broadest protection against credential leaks and is a relatively simple fix. This approach stops new leaks at the source and can be combined with a migration path to clean up existing sessions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Session capture leaks API credentials in plaintext to session_*.json [1 pull requests, 2 comments, 2 participants]