Skip to content

fix: stop leaking raw exception text to clients, log it server-side - #144

Open
shrdgn wants to merge 1 commit into
mainfrom
claude/server-error-leak-logging
Open

fix: stop leaking raw exception text to clients, log it server-side#144
shrdgn wants to merge 1 commit into
mainfrom
claude/server-error-leak-logging

Conversation

@shrdgn

@shrdgn shrdgn commented Aug 14, 2026

Copy link
Copy Markdown
Owner

What & why

openfusion/server.py has zero logging calls anywhere in the file (confirmed via grep — every other module that needs it, e.g. router.py, upstream.py, has a module logger). The catch-all except Exception in chat_completions (previously server.py:595-599) both:

  1. Leaked internal detail to clients: it wrapped the raw exception in UpstreamError(str(exc)), which errors.py puts verbatim into the client-facing JSON error.message field. Any bug in the fusion path — a KeyError, AttributeError, a pydantic validation error, anything that isn't a real upstream failure — surfaced its str(exc) straight to the caller.
  2. Left operators blind: since there was no logging anywhere in the file, that same bug left zero server-side trace. The only place a real code defect's cause disappeared was this handler.

This also mislabeled genuine internal bugs as upstream_error (misleading for anyone debugging from the client side), though that error type is left as-is here since fixing that taxonomy is a separate, larger change.

Fix: add a module logger (_log = logging.getLogger(__name__), matching the router.py convention) and call _log.exception(...) before responding with a generic, non-leaking message. The already-safe branches above it (OpenFusionError, json.JSONDecodeError) are untouched — this only changes the final catch-all for truly unexpected exceptions.

Found via an automated repo-review scheduled task; verified by hand by reading server.py's exception handling and confirming (via grep) it was the only module with no logger.

How it was tested

  • ruff check . passes (my changed lines; two pre-existing, unrelated formatting/lint items elsewhere in the repo are untouched by this diff)
  • pytest -q passes (491 passed, no live network)
  • New behavior has a test — tests/test_integration.py::test_chat_completions_unhandled_exception_is_logged_not_leaked asserts the client response no longer contains the raw exception text and that it's still captured server-side via caplog. Also updated tests/test_errors.py::test_unexpected_exception_returns_upstream_error, which previously asserted the leaky behavior as correct.
  • mypy openfusion/server.py passes
  • Docs updated — not needed, no config/request-surface/defaults changed
  • No secrets, prompts, or response bodies added to logs or metrics — this change removes a leak, it doesn't add new logged payloads (only the exception's own message/traceback, same as any other module's logging.exception)
  • Bench number — not applicable, no quality/cost/behavior change to model calls

Notes for reviewers

Only chat_completions's final catch-all is touched. The streaming path (_fusion_stream etc.) already wraps its own try/except into an SSE error chunk with a generic-ish message, so it wasn't affected by this gap.


Generated by Claude Code

server.py's catch-all handler in chat_completions had no logging.getLogger
call anywhere in the file, so a bug in the fusion path (KeyError,
AttributeError, a pydantic error, etc.) vanished without a server-side
trace and its str(exc) went verbatim into the client-facing JSON error
body. Log the exception and return a generic message instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUqjSHNBmuHjXmw7o9QKLz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants