From aab2e99bae63bfd7f780a6c08dce6ff82f8426c6 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 15 Jun 2026 05:19:34 -0700 Subject: [PATCH] test: cover request debug dump redaction Keep request dump writes on the shared atomic JSON path, add regression coverage for request body/error/stdout redaction, and map the salvaged contributor email for release attribution. --- agent/agent_runtime_helpers.py | 11 ++-- scripts/release.py | 1 + tests/agent/test_redact.py | 4 ++ .../test_run_agent_codex_responses.py | 52 +++++++++++++++++++ 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index b0ea2f62112..884866dc117 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -1223,18 +1223,17 @@ def dump_api_request_debug( # 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. Atomicity preserved via temp-file + Path.replace. + # 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 = redact_sensitive_text(_serialized, force=True) - _tmp = dump_file.with_name(dump_file.name + ".tmp") - _tmp.write_text(_redacted, encoding="utf-8") - _tmp.replace(dump_file) + _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(_redacted) + print(json.dumps(_redacted_payload, ensure_ascii=False, indent=2, default=str)) return dump_file except Exception as dump_error: diff --git a/scripts/release.py b/scripts/release.py index 5d4dfcfa3fe..a21bd36ab54 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -46,6 +46,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { "kenmege@yahoo.com": "Kenmege", + "tianying.x@eukarya.io": "xtymac", "dkobi16@gmail.com": "Diyoncrz18", "arnaud@nolimitdevelopment.com": "ali-nld", "sswdarius@gmail.com": "necoweb3", diff --git a/tests/agent/test_redact.py b/tests/agent/test_redact.py index e956b2a3ab9..472b97fb395 100644 --- a/tests/agent/test_redact.py +++ b/tests/agent/test_redact.py @@ -53,6 +53,10 @@ class TestKnownPrefixes: 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 diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index a031907611c..14e01d9fecd 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/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="sk-ant-providersecret1234567890", + quiet_mode=True, + max_iterations=1, + skip_context_files=True, + skip_memory=True, + ) + agent.logs_dir = tmp_path + + notion_token = "ntn_abc123def456ghi789jkl" + error_secret = "sk-ant-errorsecret1234567890" + response_secret = "sk-ant-responsesecret1234567890" + 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) ---