fix(security): sanitize error responses to prevent information leakage (#5)

* fix(security): sanitize error responses to prevent information leakage

Replace raw error messages in HTTP responses with generic messages.
Internal error details (stack traces, module paths, error messages)
were being returned to clients in 4 gateway endpoints.

* fix: sanitize 2 additional error response leaks in openresponses-http

Address CodeRabbit feedback: non-stream and streaming error paths in
openresponses-http.ts were still returning String(err) to clients.

* fix: add server-side error logging to sanitized catch blocks

Restore err parameter and add logWarn() calls so errors are still
captured server-side for diagnostics while keeping client responses
sanitized. Addresses CodeRabbit feedback about silently discarded errors.
This commit is contained in:
David Rudduck 2026-02-09 16:09:29 +10:00 committed by Peter Steinberger
parent de7d94d9e2
commit f788de30c8
3 changed files with 16 additions and 7 deletions

View File

@ -5,6 +5,7 @@ import { buildHistoryContextFromEntries, type HistoryEntry } from "../auto-reply
import { createDefaultDeps } from "../cli/deps.js";
import { agentCommand } from "../commands/agent.js";
import { emitAgentEvent, onAgentEvent } from "../infra/agent-events.js";
import { logWarn } from "../logger.js";
import { defaultRuntime } from "../runtime.js";
import { authorizeGatewayConnect, type ResolvedGatewayAuth } from "./auth.js";
import {
@ -264,8 +265,9 @@ export async function handleOpenAiHttpRequest(
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
});
} catch (err) {
logWarn(`openai-compat: chat completion failed: ${String(err)}`);
sendJson(res, 500, {
error: { message: String(err), type: "api_error" },
error: { message: "internal error", type: "api_error" },
});
}
return true;
@ -394,6 +396,7 @@ export async function handleOpenAiHttpRequest(
});
}
} catch (err) {
logWarn(`openai-compat: streaming chat completion failed: ${String(err)}`);
if (closed) {
return;
}
@ -405,7 +408,7 @@ export async function handleOpenAiHttpRequest(
choices: [
{
index: 0,
delta: { content: `Error: ${String(err)}` },
delta: { content: "Error: internal error" },
finish_reason: "stop",
},
],

View File

@ -16,6 +16,7 @@ import { buildHistoryContextFromEntries, type HistoryEntry } from "../auto-reply
import { createDefaultDeps } from "../cli/deps.js";
import { agentCommand } from "../commands/agent.js";
import { emitAgentEvent, onAgentEvent } from "../infra/agent-events.js";
import { logWarn } from "../logger.js";
import {
DEFAULT_INPUT_FILE_MAX_BYTES,
DEFAULT_INPUT_FILE_MAX_CHARS,
@ -485,8 +486,9 @@ export async function handleOpenResponsesHttpRequest(
}
}
} catch (err) {
logWarn(`openresponses: request parsing failed: ${String(err)}`);
sendJson(res, 400, {
error: { message: String(err), type: "invalid_request_error" },
error: { message: "invalid request", type: "invalid_request_error" },
});
return true;
}
@ -502,8 +504,9 @@ export async function handleOpenResponsesHttpRequest(
resolvedClientTools = toolChoiceResult.tools;
toolChoicePrompt = toolChoiceResult.extraSystemPrompt;
} catch (err) {
logWarn(`openresponses: tool configuration failed: ${String(err)}`);
sendJson(res, 400, {
error: { message: String(err), type: "invalid_request_error" },
error: { message: "invalid tool configuration", type: "invalid_request_error" },
});
return true;
}
@ -617,12 +620,13 @@ export async function handleOpenResponsesHttpRequest(
sendJson(res, 200, response);
} catch (err) {
logWarn(`openresponses: non-stream response failed: ${String(err)}`);
const response = createResponseResource({
id: responseId,
model,
status: "failed",
output: [],
error: { code: "api_error", message: String(err) },
error: { code: "api_error", message: "internal error" },
});
sendJson(res, 500, response);
}
@ -912,6 +916,7 @@ export async function handleOpenResponsesHttpRequest(
});
}
} catch (err) {
logWarn(`openresponses: streaming response failed: ${String(err)}`);
if (closed) {
return;
}
@ -922,7 +927,7 @@ export async function handleOpenResponsesHttpRequest(
model,
status: "failed",
output: [],
error: { code: "api_error", message: String(err) },
error: { code: "api_error", message: "internal error" },
usage: finalUsage,
});

View File

@ -348,9 +348,10 @@ export async function handleToolsInvokeHttpRequest(
const result = await (tool as any).execute?.(`http-${Date.now()}`, toolArgs);
sendJson(res, 200, { ok: true, result });
} catch (err) {
logWarn(`tools-invoke: tool execution failed: ${String(err)}`);
sendJson(res, 400, {
ok: false,
error: { type: "tool_error", message: err instanceof Error ? err.message : String(err) },
error: { type: "tool_error", message: "tool execution failed" },
});
}