hermes - ✅(Solved) Fix [Feature]: request_dump_*.json files in ~/.hermes/sessions/ contain plaintext Authorization headers — agent/redact.py exists but isn't applied on this path [3 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#18707Fetched 2026-05-03 04:54:48
View on GitHub
Comments
2
Participants
2
Timeline
10
Reactions
0
Timeline (top)
cross-referenced ×3labeled ×3commented ×2referenced ×2

When a chat request to a provider fails, Hermes writes a JSON dump of the entire outgoing request to ~/.hermes/sessions/request_dump_<session>_<timestamp>.json, including the complete Authorization: Bearer <api-key> header value in plaintext.

The project already ships and uses agent/redact.py for redaction elsewhere (verified by grep -r redact hermes-agent/), but the dump path bypasses it.

This is a security issue — debug dumps of failed requests contain credentials in a hidden directory under the operator's home.

Error Message

how OpenAI itself redacts in error messages, which still preserves

Root Cause

These files sit in a hidden directory with no visual indicator. They are particularly likely to be exfiltrated because they exist:

  • An operator preparing a bug report does:
  tar -czf hermes-bug-report.tar.gz ~/.hermes/

and the archive lands in a Slack thread, GitHub issue, or zendesk ticket — exposing the API key.

  • Anyone with backup access (rsync ~/.hermes/ → backup-server) gets every key the operator has ever used.

  • grep -r 'sk-proj-' ~/.hermes/ returns hits in dump files even after the operator believes they've rotated the key, because dumps preserve the moment of failure with the old key.

These dumps are unusually high-risk because they are failed requests — an operator is most likely to inspect, share, or upload them when debugging.

Fix Action

Fixed

PR fix notes

PR #18735: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Summary

This automated PR resolves 7 identified upstream issues focusing on reliability, cross-platform behavior, and security hardening.

Resolved issues

  1. #18722 — cron jobs with next_run_at: null now recover for recurring schedules; scheduler now tolerates non-dict origin values.

    • Files: cron/jobs.py, cron/scheduler.py, tests/cron/test_jobs.py, tests/cron/test_scheduler.py
  2. #18705 — dotenv loading no longer overrides runtime-injected environment variables.

    • Files: hermes_cli/env_loader.py, tests/hermes_cli/test_env_loader.py
  3. #18659scan_skill_commands no longer clears cached commands before a successful rescan.

    • Files: agent/skill_commands.py
  4. #18675 — skill fallback file scan now skips heavy dependency directories and enforces a file cap.

    • Files: agent/skill_commands.py
  5. #18617 — context compressor now synchronizes threshold_percent correctly across model switch, fallback activation, and primary restoration.

    • Files: run_agent.py
  6. #18681 — custom provider /model path now correctly carries provider credentials during model verification path in this branch baseline (already included in upstream branch state; preserved in final branch history).

    • Files: gateway/run.py (resolved in branch baseline)
  7. #18707 — request debug dumps are now redacted before writing to disk/stdout to avoid plaintext secret leakage.

    • Files: run_agent.py

Validation

  • python3 -m py_compile run_agent.py cron/jobs.py cron/scheduler.py hermes_cli/env_loader.py agent/skill_commands.py gateway/run.py
  • pytest -n 0 tests/hermes_cli/test_env_loader.py tests/gateway/test_model_command_custom_providers.py tests/cron/test_jobs.py::TestGetDueJobs::test_broken_cron_without_next_run_is_recovered tests/cron/test_scheduler.py::TestResolveOrigin::test_non_dict_origin_tolerated tests/agent/test_skill_commands.py tests/agent/test_skill_commands_reload.py

Changed files

  • Dockerfile (modified, +3/-2)
  • acp_adapter/session.py (modified, +12/-0)
  • agent/auxiliary_client.py (modified, +280/-28)
  • agent/context_compressor.py (modified, +496/-52)
  • agent/skill_commands.py (modified, +18/-4)
  • agent/title_generator.py (modified, +2/-2)
  • agent/transports/chat_completions.py (modified, +14/-0)
  • agent/usage_pricing.py (modified, +4/-0)
  • cli-config.yaml.example (modified, +5/-0)
  • cli.py (modified, +27/-3)
  • cron/jobs.py (modified, +13/-2)
  • cron/scheduler.py (modified, +14/-4)
  • docker/entrypoint.sh (modified, +9/-1)
  • gateway/channel_directory.py (modified, +14/-4)
  • gateway/platforms/discord.py (modified, +33/-7)
  • gateway/platforms/email.py (modified, +12/-2)
  • gateway/platforms/feishu.py (modified, +34/-1)
  • gateway/platforms/qqbot/adapter.py (modified, +8/-2)
  • gateway/platforms/telegram_network.py (modified, +7/-2)
  • gateway/platforms/weixin.py (modified, +10/-1)
  • gateway/run.py (modified, +129/-32)
  • gateway/status.py (modified, +8/-1)
  • hermes_cli/auth.py (modified, +2/-2)
  • hermes_cli/commands.py (modified, +1/-1)
  • hermes_cli/config.py (modified, +271/-40)
  • hermes_cli/copilot_auth.py (modified, +1/-1)
  • hermes_cli/doctor.py (modified, +6/-1)
  • hermes_cli/env_loader.py (modified, +5/-4)
  • hermes_cli/gateway.py (modified, +16/-13)
  • hermes_cli/main.py (modified, +69/-3)
  • hermes_cli/memory_setup.py (modified, +1/-1)
  • hermes_cli/model_switch.py (modified, +6/-1)
  • hermes_cli/models.py (modified, +60/-2)
  • hermes_cli/profiles.py (modified, +16/-3)
  • hermes_cli/runtime_provider.py (modified, +16/-13)
  • hermes_cli/setup.py (modified, +8/-2)
  • hermes_cli/slack_cli.py (modified, +1/-2)
  • hermes_cli/status.py (modified, +17/-2)
  • hermes_cli/web_server.py (modified, +1/-1)
  • hermes_constants.py (modified, +16/-3)
  • model_tools.py (modified, +44/-13)
  • run_agent.py (modified, +408/-84)
  • setup-hermes.sh (modified, +23/-12)
  • skills/red-teaming/godmode/scripts/load_godmode.py (modified, +9/-8)
  • tests/agent/test_context_compressor.py (modified, +389/-0)
  • tests/agent/transports/test_chat_completions.py (modified, +11/-0)
  • tests/cron/test_jobs.py (modified, +26/-0)
  • tests/cron/test_scheduler.py (modified, +4/-0)
  • tests/gateway/test_compress_command.py (modified, +49/-0)
  • tests/hermes_cli/test_api_key_providers.py (modified, +5/-5)
  • tests/hermes_cli/test_config.py (modified, +17/-0)
  • tests/hermes_cli/test_env_loader.py (modified, +6/-6)
  • tests/run_agent/test_413_compression.py (modified, +81/-1)
  • tests/run_agent/test_compression_boundary_hook.py (modified, +42/-0)
  • tests/run_agent/test_run_agent.py (modified, +100/-13)
  • tests/tools/test_skill_manager_tool.py (modified, +270/-0)
  • tools/approval.py (modified, +1/-1)
  • tools/delegate_tool.py (modified, +4/-1)
  • tools/environments/docker.py (modified, +36/-5)
  • tools/environments/local.py (modified, +8/-1)
  • tools/file_operations.py (modified, +70/-67)
  • tools/file_tools.py (modified, +13/-2)
  • tools/send_message_tool.py (modified, +72/-2)
  • tools/session_search_tool.py (modified, +2/-2)
  • tools/skill_manager_tool.py (modified, +82/-21)
  • tools/skills_tool.py (modified, +13/-1)
  • tools/terminal_tool.py (modified, +6/-0)
  • tools/tool_backend_helpers.py (modified, +15/-5)
  • tools/tts_tool.py (modified, +27/-16)
  • tools/voice_mode.py (modified, +23/-10)
  • toolsets.py (modified, +14/-1)
  • tui_gateway/server.py (modified, +5/-3)
  • ui-tui/src/app/turnController.ts (modified, +1/-1)
  • ui-tui/src/app/useInputHandlers.ts (modified, +8/-3)
  • ui-tui/src/app/useSessionLifecycle.ts (modified, +1/-1)
  • ui-tui/src/gatewayTypes.ts (modified, +1/-0)
  • utils.py (modified, +9/-0)
  • uv.lock (modified, +161/-2)
  • website/docs/reference/environment-variables.md (modified, +1/-1)

PR #18750: fix(agent): redact secrets in request_dump_*.json files

Description (problem / solution / changelog)

What does this PR do?

_dump_api_request_debug() writes ~/.hermes/sessions/request_dump_*.json containing the live request body, headers, and provider error response. Two leak channels existed:

  1. Authorization header used the partial _mask_api_key_for_logs(), which keeps key[:8] and key[-4:] visible — enough to recognise an sk-proj-* prefix in a shared bug report. Tightened to Bearer ***.
  2. Body, error.body, error.response_text were written raw. Pasted credentials in user messages, keys quoted back in 401 bodies, and any other in-payload secret survived to disk unredacted.

The agent already ships agent/redact.py with regex coverage for every known provider key prefix and ENV=value / JSON-field assignment patterns — it just wasn't wired into this path.

This PR walks dump_payload recursively and applies redact_sensitive_text(value, force=True) to leaf string values before serialisation. Walking the structure (rather than the serialised text) is important: _ENV_ASSIGN_RE's greedy \S+ value group can otherwise consume past a JSON string's closing quote and corrupt the file. Operating on individual values keeps every regex bounded to a single string. force=True because this is a security boundary, not a logging preference: the dump must be safe regardless of the user's global security.redact_secrets setting.

This makes request_dump_*.json safe to attach to a GitHub issue or include in a tar -czf hermes.tar.gz ~/.hermes support bundle.

Related Issue

Fixes #18707

Type of Change

  • 🔒 Security fix

Changes Made

  • run_agent.py::_dump_api_request_debug — drop the _mask_api_key_for_logs(api_key) Authorization construction (and the now-unused api_key extraction); hard-code Bearer ***; recursively scrub leaf string values via redact_sensitive_text(force=True) before json.dumps; reuse the serialised string for both file write and the optional stdout echo
  • tests/run_agent/test_run_agent_codex_responses.py — add 5 redaction tests:
    • test_dump_redacts_sk_proj_key_in_authorization_header
    • test_dump_redacts_keys_embedded_in_request_body
    • test_dump_redacts_keys_in_error_response_text
    • test_dump_preserves_non_secret_metadata
    • test_dump_remains_valid_json_after_redaction (regression for the OPENAI_API_KEY=sk-proj-... in body case that, with text-level redaction, would have produced unparseable JSON)

How to Test

  1. python -m pytest tests/run_agent/test_run_agent_codex_responses.py -k dump -q — 7/7 pass (5 new + 2 existing URL tests)
  2. python -m pytest tests/run_agent/test_run_agent_codex_responses.py -q — full file 64/64 pass
  3. Manual: set OPENAI_API_KEY to a valid-format-but-revoked key, send any chat message; inspect the resulting request_dump_*.json — Authorization shows Bearer ***, message bodies with embedded sk-... strings show masked tokens, and json.loads(open(dump_file).read()) succeeds

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/run_agent/test_run_agent_codex_responses.py -q and all tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 15.2

Documentation & Housekeeping

  • I've updated relevant documentation — N/A (inline rationale in code comments)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact — N/A (path is platform-agnostic)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Changed files

  • run_agent.py (modified, +33/-12)
  • tests/run_agent/test_run_agent_codex_responses.py (modified, +148/-0)

PR #18909: fix(security): strip credentials from request debug dumps

Description (problem / solution / changelog)

Summary

_dump_api_request_debug() wrote the full api_kwargs body to ~/.hermes/sessions/request_dump_*.json, including credential material. These files are commonly shared in bug reports, exposing API keys.

Root Cause

The dump function only stripped timeout from the body before writing. Any credential material in api_kwargs (e.g. api_key, extra_headers with Authorization) was written in plaintext.

Fix

Strip known credential keys from the body before serialising:

Stripped from bodyReason
api_keySDK may pass key in kwargs
x_api_keyGemini-style header key
api_tokenGeneric auth token
auth_tokenGeneric auth token
extra_headersDict may contain Authorization/X-API-Key
headersSame as extra_headers

Authorization header in the synthetic request section was already masked via _mask_api_key_for_logs() — no change there.

Testing

8 new tests in test_dump_credential_sanitisation.py:

  • api_key stripped from body
  • x_api_key stripped from body
  • extra_headers dict stripped from body
  • headers dict stripped from body
  • Authorization header masked in request headers
  • timeout stripped from body
  • normal fields (model, messages, temperature) preserved
  • end-to-end: entire serialised JSON contains no raw secrets

307 existing run_agent tests: all pass, zero regression.

Relation to prior PRs

  • #8533 (OPEN) — only pops body keys, misses headers dict
  • #8768 (OPEN) — removes Authorization header but also removes api_key extraction

This PR is a superset: strips both body keys AND header dicts, keeps the masked Authorization for debug utility.

Closes #8518 Fixes #18707

Changed files

  • run_agent.py (modified, +16/-2)
  • tests/run_agent/test_dump_credential_sanitisation.py (added, +172/-0)

Code Example

ls -la ~/.hermes/sessions/request_dump_*.json

---

tar -czf hermes-bug-report.tar.gz ~/.hermes/

---
RAW_BUFFERClick to expand / collapse

Problem or Use Case

Summary

When a chat request to a provider fails, Hermes writes a JSON dump of the entire outgoing request to ~/.hermes/sessions/request_dump_<session>_<timestamp>.json, including the complete Authorization: Bearer <api-key> header value in plaintext.

The project already ships and uses agent/redact.py for redaction elsewhere (verified by grep -r redact hermes-agent/), but the dump path bypasses it.

This is a security issue — debug dumps of failed requests contain credentials in a hidden directory under the operator's home.

Reproduction

  1. Set OPENAI_API_KEY to a valid-format but incorrect value (e.g. revoked key)
  2. Send any chat message
  3. After the 401 from OpenAI, inspect:
ls -la ~/.hermes/sessions/request_dump_*.json

In our test, 5 dump files were created from a single failed session, each containing the complete sk-proj-... key in the Authorization header.

Why this matters

These files sit in a hidden directory with no visual indicator. They are particularly likely to be exfiltrated because they exist:

  • An operator preparing a bug report does:
  tar -czf hermes-bug-report.tar.gz ~/.hermes/

and the archive lands in a Slack thread, GitHub issue, or zendesk ticket — exposing the API key.

  • Anyone with backup access (rsync ~/.hermes/ → backup-server) gets every key the operator has ever used.

  • grep -r 'sk-proj-' ~/.hermes/ returns hits in dump files even after the operator believes they've rotated the key, because dumps preserve the moment of failure with the old key.

These dumps are unusually high-risk because they are failed requests — an operator is most likely to inspect, share, or upload them when debugging.

Suggested fix

Apply agent/redact.py (or equivalent) to the dump payload before writing.

Specifically, redact:

  • Headers (case-insensitive): Authorization, X-API-Key, apikey, api-key, Cookie, any header matching *-API-Key or *-Token
  • Values matching credential patterns:
    • sk-proj-[A-Za-z0-9_-]{40,} (OpenAI)
    • sk-ant-[A-Za-z0-9_-]{40,} (Anthropic)
    • AIza[A-Za-z0-9_-]{35} (Google)
    • xai-[A-Za-z0-9_-]{40,} (xAI)
    • gsk_[A-Za-z0-9_-]{40,} (Groq)
    • and any matching credential entropy + length

Keep first 8 + last 4 bytes, replace middle with *** — matching how OpenAI itself redacts in error messages, which still preserves debug utility (you can tell which key was used) without exposing the secret.

Even better: opt-in dumps

Currently dumps appear to write by default for any failed request. Consider gating behind HERMES_DUMP_FAILED_REQUESTS=1 (default off). Operators who need them enable explicitly. This also removes the ambient "files I didn't ask for are growing in my home dir" surprise.

Environment

  • Hermes 0.12.0 (hermes-agent commit f903ceec)
  • Python 3.11.15
  • 5 dump files generated from a single 4-message failed chat session

Proposed Solution

Alternatives Considered

No response

Feature Type

New tool

Scope

None

Contribution

  • I'd like to implement this myself and submit a PR

Debug Report (optional)

extent analysis

TL;DR

Apply the existing agent/redact.py to redact sensitive information from the request dump files to prevent exposure of API keys.

Guidance

  • Review the agent/redact.py script to understand the redaction logic and ensure it covers all necessary cases, including the specified headers and credential patterns.
  • Modify the code that writes the request dump files to apply the redaction before writing, using the patterns and logic from agent/redact.py.
  • Consider implementing an opt-in mechanism, such as the suggested HERMES_DUMP_FAILED_REQUESTS environment variable, to control the creation of dump files and reduce the risk of unintended exposure.
  • Verify that the redaction is working correctly by inspecting the resulting dump files and ensuring that sensitive information is properly masked.

Example

import redact

# Assuming 'request' is the object being dumped
redacted_request = redact.redact_request(request)
with open(f'request_dump_{session}_{timestamp}.json', 'w') as f:
    json.dump(redacted_request, f)

Notes

The provided agent/redact.py script and the suggested redaction patterns should be reviewed and tested to ensure they cover all necessary cases and do not introduce any unintended consequences.

Recommendation

Apply the workaround by modifying the code to use the existing agent/redact.py script to redact sensitive information from the request dump files, as this directly addresses the security issue without requiring significant changes to the existing codebase.

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 [Feature]: request_dump_*.json files in ~/.hermes/sessions/ contain plaintext Authorization headers — agent/redact.py exists but isn't applied on this path [3 pull requests, 2 comments, 2 participants]