Skip to content

fix(openai): sanitize websocket errors before request logging - #203

Open
warelik wants to merge 1 commit into
kaitranntt:mainfrom
warelik:fix/websocket-error-log-sanitization
Open

fix(openai): sanitize websocket errors before request logging#203
warelik wants to merge 1 commit into
kaitranntt:mainfrom
warelik:fix/websocket-error-log-sanitization

Conversation

@warelik

@warelik warelik commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

forwardResponsesWebsocket passed two upstream-derived errors to LoggingAPIResponseError verbatim, before the redaction that writeResponsesWebsocketTerminalError applies to the client-facing frame:

  • the executor error delivered on the errs channel (sdk/api/handlers/openai/openai_responses_websocket_forward.go:69), and
  • the terminal error event payload (:132).

With RequestLog enabled the request logger stores whatever it receives (sdk/api/handlers/handlers_errors.go:164-170), so a raw upstream body could land in the log — including a credential the upstream echoed back.

Surfaced by the upstream review at router-for-me/CLIProxyAPI#4881 (discussion_r3827803235); the same code already lives on main here, so this is the Plus-side hardening for it.

Fix

  • Both call sites now pass the message through sanitizeResponsesStreamErrorMessage, the same trust-boundary sanitizer used elsewhere in this package.
  • redactResponsesStreamErrorText now applies the bearer pattern BEFORE the key/value pattern (sdk/api/handlers/openai/openai_responses_handlers.go). Previously the key/value pattern matched Bearer itself as the value of an Authorization: header and redacted only that word, leaving the token behind in the clear.

The locally constructed stream closed before response.completed message is intentionally left unsanitized — it contains no upstream content.

Tests

TestForwardResponsesWebsocketSanitizesLoggedUpstreamError and TestForwardResponsesWebsocketSanitizesLoggedErrorPayload (new file openai_responses_websocket_forward_sanitization_test.go) drive the forwarder over a real websocket pair with a credential-bearing upstream error and assert the recorded API_RESPONSE_ERROR is redacted.

Reverse bite-checks:

  1. Restoring both verbatim call sites (redaction swap kept):
--- FAIL: TestForwardResponsesWebsocketSanitizesLoggedErrorPayload (0.00s)
    openai_responses_websocket_forward_sanitization_test.go:119: request log stored the credential verbatim: "{\"type\":\"error\",\"status\":400,\"error\":{\"type\":\"invalid_request_error\",\"message\":\"bad request: Authorization: Bearer sk-forward-payload-secret\"}}"
FAIL
FAIL	github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai	0.821s
FAIL
  1. Restoring the old redaction order only (call-site wraps kept):
--- FAIL: TestForwardResponsesWebsocketSanitizesLoggedUpstreamError (0.00s)
    openai_responses_websocket_forward_sanitization_test.go:102: request log stored the credential verbatim: "upstream rejected request: Authorization: [REDACTED] sk-forward-errs-secret"
--- FAIL: TestForwardResponsesWebsocketSanitizesLoggedErrorPayload (0.00s)
    openai_responses_websocket_forward_sanitization_test.go:119: request log stored the credential verbatim: "{\"error\":{\"type\":\"invalid_request_error\",\"message\":\"bad request: Authorization: [REDACTED] sk-forward-payload-secret\"}}"
FAIL
FAIL	github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai	0.715s

With the fix, go build ./..., go vet ./sdk/api/handlers/openai/... and go test -count=1 ./sdk/api/handlers/openai/... are green.

forwardResponsesWebsocket passed the executor's ErrorMessage and the
terminal error payload message to LoggingAPIResponseError verbatim, before
the redaction that writeResponsesWebsocketTerminalError applies to the
client-facing frame. With RequestLog enabled the request log then stored
the raw upstream body, which can echo the credential sent upstream.

Wrap both call sites in sanitizeOpenAIErrorMessage, the same trust-boundary
sanitizer the other OpenAI handlers use before writing error responses.

Covered by TestForwardResponsesWebsocketSanitizesLoggedUpstreamError and
TestForwardResponsesWebsocketSanitizesLoggedErrorPayload, which drive the
forwarder over a real websocket pair with a credential-bearing upstream
error and assert the recorded API_RESPONSE_ERROR is redacted.
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.

1 participant