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

0open issues
0open PRs
12items grouped
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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #46637 fix(agent): redact secrets in request debug dumps

    merged to main

    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.py

    Show 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.00 means 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 Lab

At a glance

Subsystem
agent
Merged PRs
#46637
Closed issues
5
Attempts
6 PRs closed without merging
Fix link checked
15 Jun 2026

For your agent

The same facts, typed.