mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(api-server): widen error redaction to cron-endpoint + SSE sites
Follow-up to the salvaged #37733 fix. The contributor centralized redaction at _openai_error and the chat/responses failure paths, which covers the OpenAI-compatible envelopes transitively. Two sibling classes crossed the same authenticated HTTP boundary unredacted: - 8x cron-management endpoints returning {"error": str(e)} on 500 - the session-chat SSE error event ({"message": str(exc)}) Route both through the same _redact_api_error_text(force=True) helper. Add AUTHOR_MAP entry for coygeek and a TestRedactApiErrorText guard covering mask/force/limit/passthrough behavior.
This commit is contained in:
parent
5e774de76e
commit
58c36b1798
3 changed files with 39 additions and 9 deletions
|
|
@ -1768,7 +1768,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
}))
|
||||
except Exception as exc:
|
||||
logger.exception("[api_server] session chat stream failed")
|
||||
await queue.put(_event_payload("error", {"message": str(exc)}))
|
||||
await queue.put(_event_payload("error", {"message": _redact_api_error_text(exc)}))
|
||||
finally:
|
||||
await queue.put(_event_payload("done", {}))
|
||||
await queue.put(None)
|
||||
|
|
@ -3297,7 +3297,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
jobs = _cron_list(include_disabled=include_disabled)
|
||||
return web.json_response({"jobs": jobs})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_create_job(self, request: "web.Request") -> "web.Response":
|
||||
"""POST /api/jobs — create a new cron job."""
|
||||
|
|
@ -3351,7 +3351,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
_notify_cron_provider_jobs_changed()
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_get_job(self, request: "web.Request") -> "web.Response":
|
||||
"""GET /api/jobs/{job_id} — get a single cron job."""
|
||||
|
|
@ -3370,7 +3370,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
return web.json_response({"error": "Job not found"}, status=404)
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_update_job(self, request: "web.Request") -> "web.Response":
|
||||
"""PATCH /api/jobs/{job_id} — update a cron job."""
|
||||
|
|
@ -3408,7 +3408,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
_notify_cron_provider_jobs_changed()
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_delete_job(self, request: "web.Request") -> "web.Response":
|
||||
"""DELETE /api/jobs/{job_id} — delete a cron job."""
|
||||
|
|
@ -3428,7 +3428,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
_notify_cron_provider_jobs_changed()
|
||||
return web.json_response({"ok": True})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_pause_job(self, request: "web.Request") -> "web.Response":
|
||||
"""POST /api/jobs/{job_id}/pause — pause a cron job."""
|
||||
|
|
@ -3448,7 +3448,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
_notify_cron_provider_jobs_changed()
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_resume_job(self, request: "web.Request") -> "web.Response":
|
||||
"""POST /api/jobs/{job_id}/resume — resume a paused cron job."""
|
||||
|
|
@ -3468,7 +3468,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
_notify_cron_provider_jobs_changed()
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_run_job(self, request: "web.Request") -> "web.Response":
|
||||
"""POST /api/jobs/{job_id}/run — trigger immediate execution."""
|
||||
|
|
@ -3487,7 +3487,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
return web.json_response({"error": "Job not found"}, status=404)
|
||||
return web.json_response({"job": job})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
return web.json_response({"error": _redact_api_error_text(e)}, status=500)
|
||||
|
||||
async def _handle_cron_fire(self, request: "web.Request") -> "web.Response":
|
||||
"""POST /api/cron/fire — Chronos managed-cron fire webhook (NAS → agent).
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ AUTHOR_MAP = {
|
|||
"267614622+agt-user@users.noreply.github.com": "agt-user", # PR #48496 salvage (telegram CLOSE-WAIT polling heartbeat, #48495)
|
||||
"80915+DavidMetcalfe@users.noreply.github.com": "DavidMetcalfe", # PR #52272 salvage (route reasoning-model thinking-timeouts to timeout not context_overflow + reasoning-specific guidance; #52271)
|
||||
"66773372+Tranquil-Flow@users.noreply.github.com": "Tranquil-Flow", # PR #52623 salvage (auxiliary Anthropic base_url host validation; #52608)
|
||||
"65363919+coygeek@users.noreply.github.com": "coygeek", # PR #37735 salvage (redact provider error text at api-server HTTP boundary; #37733)
|
||||
"moonsong@nousresearch.local": "Tranquil-Flow", # PR #52623 salvage (auxiliary Anthropic base_url host validation; #52608)
|
||||
"140971685+Dr1985@users.noreply.github.com": "Dr1985", # PR #42567 salvage (launchd supervision detection + status reporting; #42524)
|
||||
"8180647+herbalizer404@users.noreply.github.com": "herbalizer404", # PR #49076 + #51835 salvage (auxiliary compression fallback: 403/session-usage payment errors + honor fallback chain when aux provider auth unavailable)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from gateway.platforms.api_server import (
|
|||
ResponseStore,
|
||||
_IdempotencyCache,
|
||||
_derive_chat_session_id,
|
||||
_redact_api_error_text,
|
||||
check_api_server_requirements,
|
||||
cors_middleware,
|
||||
security_headers_middleware,
|
||||
|
|
@ -50,6 +51,34 @@ class TestCheckRequirements:
|
|||
assert check_api_server_requirements() is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _redact_api_error_text — guards every outward error site (envelopes, SSE
|
||||
# error events, cron-endpoint 500 bodies) that routes raw exception text to
|
||||
# authenticated HTTP clients. #37733
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestRedactApiErrorText:
|
||||
def test_masks_secret_value_but_preserves_structure(self):
|
||||
secret = "sk-api-server-leak-1234567890"
|
||||
out = _redact_api_error_text(Exception(f"auth failed OPENAI_API_KEY={secret}"))
|
||||
assert secret not in out
|
||||
assert "OPENAI_API_KEY=" in out
|
||||
|
||||
def test_redacts_regardless_of_global_redaction_setting(self):
|
||||
# force=True must mask even when global redaction is disabled.
|
||||
secret = "sk-forced-redaction-0987654321"
|
||||
with patch("agent.redact._REDACT_ENABLED", False):
|
||||
out = _redact_api_error_text(Exception(f"boom AWS_SECRET_ACCESS_KEY={secret}"))
|
||||
assert secret not in out
|
||||
|
||||
def test_limit_truncates_after_redaction(self):
|
||||
assert len(_redact_api_error_text("x" * 500, limit=50)) == 50
|
||||
|
||||
def test_clean_text_passes_through_unchanged(self):
|
||||
assert _redact_api_error_text("Job not found") == "Job not found"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ResponseStore
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue