Redact API credentials from request debug dump files
NousResearch/hermes-agent · cluster 8518 · agent
Data as of
Nothing open on GitHub. Fix merged in #46637. Check it covers your case.
The short answer
- Known?
- Yes. 12 related items grouped here, 5 issues already closed.
- Fix event
- A fix was merged to main in PR #46637. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple reports and fixes address plaintext API keys and Authorization headers being written to request_dump_*.json and session_*.json files, with proposed solutions involving applying existing redaction logic to this code path.
Open issues0
Open PRs0
Fixes on record
-
#46637 fix(agent): redact secrets in request debug dumps
files:
agent/agent_runtime_helpers.py,agent/redact.py,scripts/release.py,tests/agent/test_redact.py,tests/run_agent/test_run_agent_codex_responses.pyShow the diff
-
agent/agent_runtime_helpers.py@@ -1217,12 +1217,23 @@ def dump_api_request_debug( timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") dump_file = agent.logs_dir / f"request_dump_{agent.session_id}_{timestamp}.json" - atomic_json_write(dump_file, dump_payload, default=str) + + # Redact secrets before persisting/printing. This dump captures the + # full request body (system prompt, tool defs, context-embedded + # values), and this path fires unconditionally on API errors — so it + # otherwise lands any context-embedded secret in cleartext on disk. + # Run the serialized dump through the same scrubber used for logs/tool + # output, then hand the resulting payload back to the shared atomic + # JSON writer so request dumps keep the same write semantics as before. + from agent.redact import redact_sensitive_text + _serialized = json.dumps(dump_payload, ensure_ascii=False, indent=2, default=str) + _redacted_payload = json.loads(redact_sensitive_text(_serialized, force=True)) + atomic_json_write(dump_file, _redacted_payload, default=str) agent._vprint(f"{agent.log_prefix}🧾 Request debug dump written to: {dump_file}") if env_var_enabled("HERMES_DUMP_REQUEST_STDOUT"): - print(json.dumps(dump_payload, ensure_ascii=False, indent=2, default=str)) + print(json.dumps(_redacted_payload, ensure_ascii=False, indent=2, default=str)) return dump_file except Exception as dump_error: -
agent/redact.py@@ -104,6 +104,7 @@ r"mem0_[A-Za-z0-9]{10,}", # Mem0 Platform API key r"brv_[A-Za-z0-9]{10,}", # ByteRover API key r"xai-[A-Za-z0-9]{30,}", # xAI (Grok) API key + r"ntn_[A-Za-z0-9]{10,}", # Notion internal integration token ] # ENV assignment patterns: KEY=value where KEY contains a secret-like name -
scripts/release.py@@ -46,6 +46,7 @@ # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { "[redacted:email]": "Kenmege", + "[redacted:email]": "xtymac", "[redacted:email]": "Diyoncrz18", "[redacted:email]": "ali-nld", "[redacted:email]": "necoweb3", -
tests/agent/test_redact.py@@ -53,6 +53,10 @@ def test_fal_key(self): result = redact_sensitive_text("fal_abc123def456ghi789jkl") assert "abc123def456" not in result + def test_notion_internal_integration_token(self): + result = redact_sensitive_text("ntn_abc123def456ghi789jkl") + assert "abc123def456" not in result + def test_short_token_fully_masked(self): result = redact_sensitive_text("key=sk-short1234567") assert "***" in result -
tests/run_agent/test_run_agent_codex_responses.py@@ -1913,6 +1913,58 @@ def test_dump_api_request_debug_uses_chat_completions_url(monkeypatch, tmp_path) assert payload["request"]["url"] == "http://127.0.0.1:9208/v1/chat/completions" +def test_dump_api_request_debug_redacts_request_and_error_secrets(monkeypatch, tmp_path, capsys): + """Request debug dumps should redact secrets before disk/stdout output.""" + import json + + _patch_agent_bootstrap(monkeypatch) + monkeypatch.setenv("HERMES_DUMP_REQUEST_STDOUT", "1") + agent = run_agent.AIAgent( + model="gpt-4o", + base_url="http://127.0.0.1:9208/v1", + api_key="[redacted:secret-assignment]", + quiet_mode=True, + max_iterations=1, + skip_context_files=True, + skip_memory=True, + ) + agent.logs_dir = tmp_path + + notion_token = "[redacted:secret-assignment]" + error_secret = "[redacted:secret-assignment]" + response_secret = "[redacted:secret-assignment]" + response = SimpleNamespace(status_code=400, text=f"provider echoed {response_secret}") + + class ProviderError(RuntimeError): + body: object + response: object + + error = ProviderError(f"bad token {error_secret}") + error.body = {"message": f"bad token {error_secret}"} + error.response = response + + dump_file = agent._dump_api_request_debug( + { + "model": "gpt-4o", + "messages": [{"role": "user", "content": f"use {notion_token}"}], + "metadata": {"NOTION_API_KEY": notion_token}, + }, + reason="provider_error", + error=error, + ) + + assert dump_file is not None + dumped_text = dump_file.read_text() + stdout_text = capsys.readouterr().out + for raw in (notion_token, error_secret, response_secret, "providersecret1234567890"): + assert raw not in dumped_text + assert raw not in stdout_text + + payload = json.loads(dumped_text) + assert payload["request"]["headers"]["Authorization"].startswith("Bearer sk-ant-p...") + assert "***" in dumped_text or "..." in dumped_text + + # --- Reasoning-only response tests (fix for empty content retry loop) ---
-
6 PRs closed without merging
From the close comments: done elsewhere 2 · duplicate 1 · superseded by another PR 1 · other 2
-
#8533 fix: strip API keys from request debug dumps (#8518) · superseded by another PR →
#8768 -
#8768 fix: omit authorization from request debug dumps · done elsewhere →
#8518,#46637 -
#18750 fix(agent): redact secrets in request_dump_*.json files · done elsewhere →
#8518,#46637 -
#18909 fix(security): strip credentials from request debug dumps · other →
#8533,#8768,#8518 -
#33322 [codex] Redact request debug dumps · other →
#18707,#19202,#8518,#46637,#46583
Show 1 more
-
#46595 fix(agent): redact secrets in API request debug dumps · duplicate →
#18750
Closed issues · 5
- Closed#8518[Security] API key exposed in request debug dumps via incomplete masking
- Closed#18707[Feature]: request_dump_*.json files in ~/.hermes/sessions/ contain plaintext Authorization headers — agent/redact.py exists but isn't applied on this path
- Closed#19202[Feature]: Failed-request dumps write complete Authorization headers (containing API keys) to ~/.hermes/sessions/request_dump_*.json in plaintext — agent/redact.py exists but isn't invoked on this path
- Closed#19845Session capture leaks API credentials in plaintext to session_*.json
- Closed#46583Hardening: redact secrets in API request debug dumps (unredacted request body, written unconditionally on API errors)
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.20: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #46637
- Closed issues
- 5
- Attempts
- 6 PRs closed without merging
- Fix link checked
- 15 Jun 2026