fix(security): sanitize error responses in OpenAI-compatible APIs

## Why

The OpenAI-compatible API endpoints (`openai-http.ts`, `openresponses-http.ts`)
were returning raw `String(err)` in error responses. This can leak sensitive
internal information to HTTP clients:

- Stack traces revealing code structure
- File paths exposing server directory layout
- Internal error messages from dependencies

This is the same class of vulnerability that was fixed in #2387 for
`server-http.ts`, but these OpenAI-compatible endpoints were missed.

## What

Replace `String(err)` with safe error messages:

- **500 errors (api_error)**: Return generic "Internal server error"
- **400 errors (invalid_request_error)**: Return `err.message` only
  (no stack trace), with fallback to "Invalid request"

## Changes

- `src/gateway/openai-http.ts`: 2 catch blocks sanitized
- `src/gateway/openresponses-http.ts`: 4 catch blocks sanitized
This commit is contained in:
s4na 2026-01-30 17:34:13 +09:00
parent 6af205a13a
commit 8d46b92e30
2 changed files with 10 additions and 10 deletions

View File

@ -239,9 +239,9 @@ export async function handleOpenAiHttpRequest(
], ],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
}); });
} catch (err) { } catch {
sendJson(res, 500, { sendJson(res, 500, {
error: { message: String(err), type: "api_error" }, error: { message: "Internal server error", type: "api_error" },
}); });
} }
return true; return true;
@ -361,7 +361,7 @@ export async function handleOpenAiHttpRequest(
], ],
}); });
} }
} catch (err) { } catch {
if (closed) return; if (closed) return;
writeSse(res, { writeSse(res, {
id: runId, id: runId,
@ -371,7 +371,7 @@ export async function handleOpenAiHttpRequest(
choices: [ choices: [
{ {
index: 0, index: 0,
delta: { content: `Error: ${String(err)}` }, delta: { content: "Error: Internal server error" },
finish_reason: "stop", finish_reason: "stop",
}, },
], ],

View File

@ -435,7 +435,7 @@ export async function handleOpenResponsesHttpRequest(
} }
} catch (err) { } catch (err) {
sendJson(res, 400, { sendJson(res, 400, {
error: { message: String(err), type: "invalid_request_error" }, error: { message: err instanceof Error ? err.message : "Invalid request", type: "invalid_request_error" },
}); });
return true; return true;
} }
@ -452,7 +452,7 @@ export async function handleOpenResponsesHttpRequest(
toolChoicePrompt = toolChoiceResult.extraSystemPrompt; toolChoicePrompt = toolChoiceResult.extraSystemPrompt;
} catch (err) { } catch (err) {
sendJson(res, 400, { sendJson(res, 400, {
error: { message: String(err), type: "invalid_request_error" }, error: { message: err instanceof Error ? err.message : "Invalid request", type: "invalid_request_error" },
}); });
return true; return true;
} }
@ -565,13 +565,13 @@ export async function handleOpenResponsesHttpRequest(
}); });
sendJson(res, 200, response); sendJson(res, 200, response);
} catch (err) { } catch {
const response = createResponseResource({ const response = createResponseResource({
id: responseId, id: responseId,
model, model,
status: "failed", status: "failed",
output: [], output: [],
error: { code: "api_error", message: String(err) }, error: { code: "api_error", message: "Internal server error" },
}); });
sendJson(res, 500, response); sendJson(res, 500, response);
} }
@ -844,7 +844,7 @@ export async function handleOpenResponsesHttpRequest(
delta: content, delta: content,
}); });
} }
} catch (err) { } catch {
if (closed) return; if (closed) return;
finalUsage = finalUsage ?? createEmptyUsage(); finalUsage = finalUsage ?? createEmptyUsage();
@ -853,7 +853,7 @@ export async function handleOpenResponsesHttpRequest(
model, model,
status: "failed", status: "failed",
output: [], output: [],
error: { code: "api_error", message: String(err) }, error: { code: "api_error", message: "Internal server error" },
usage: finalUsage, usage: finalUsage,
}); });