mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-17 09:41:58 +00:00
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.
This commit is contained in:
parent
ad58dd51ac
commit
aab2e99bae
4 changed files with 62 additions and 6 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) ---
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue