tries to disable quarantined aws keys

This commit is contained in:
nai-degen 2024-06-30 05:08:27 -05:00
parent 994b30dcce
commit edc0d094e2
2 changed files with 39 additions and 18 deletions

View File

@ -65,7 +65,7 @@ type ErrorGeneratorOptions = {
format: APIFormat | "unknown"; format: APIFormat | "unknown";
title: string; title: string;
message: string; message: string;
obj?: object; obj?: Record<string, any>;
reqId: string | number | object; reqId: string | number | object;
model?: string; model?: string;
statusCode?: number; statusCode?: number;
@ -95,6 +95,23 @@ export function tryInferFormat(body: any): APIFormat | "unknown" {
return "unknown"; return "unknown";
} }
// avoid leaking upstream hostname on dns resolution error
function redactHostname(options: ErrorGeneratorOptions): ErrorGeneratorOptions {
if (!options.message.includes("getaddrinfo")) return options;
const redacted = { ...options };
redacted.message = "Could not resolve hostname";
if (typeof redacted.obj?.error === "object") {
redacted.obj = {
...redacted.obj,
error: { message: "Could not resolve hostname" },
};
}
return redacted;
}
export function sendErrorToClient({ export function sendErrorToClient({
options, options,
req, req,
@ -104,27 +121,26 @@ export function sendErrorToClient({
req: express.Request; req: express.Request;
res: express.Response; res: express.Response;
}) { }) {
const { format: inputFormat } = options; const redactedOpts = redactHostname(options);
const { format: inputFormat } = redactedOpts;
// This is an error thrown before we know the format of the request, so we
// can't send a response in the format the client expects.
const format = const format =
inputFormat === "unknown" ? tryInferFormat(req.body) : inputFormat; inputFormat === "unknown" ? tryInferFormat(req.body) : inputFormat;
if (format === "unknown") { if (format === "unknown") {
return res.status(options.statusCode || 400).json({ return res.status(redactedOpts.statusCode || 400).json({
error: options.message, error: redactedOpts.message,
details: options.obj, details: redactedOpts.obj,
}); });
} }
const completion = buildSpoofedCompletion({ ...options, format }); const completion = buildSpoofedCompletion({ ...redactedOpts, format });
const event = buildSpoofedSSE({ ...options, format }); const event = buildSpoofedSSE({ ...redactedOpts, format });
const isStreaming = const isStreaming =
req.isStreaming || req.body.stream === true || req.body.stream === "true"; req.isStreaming || req.body.stream === true || req.body.stream === "true";
if (!res.headersSent) { if (!res.headersSent) {
res.setHeader("x-oai-proxy-error", options.title); res.setHeader("x-oai-proxy-error", redactedOpts.title);
res.setHeader("x-oai-proxy-error-status", options.statusCode || 500); res.setHeader("x-oai-proxy-error-status", redactedOpts.statusCode || 500);
} }
if (isStreaming) { if (isStreaming) {

View File

@ -1,4 +1,5 @@
/* This file is fucking horrendous, sorry */ /* This file is fucking horrendous, sorry */
// TODO: extract all per-service error response handling into its own modules
import { Request, Response } from "express"; import { Request, Response } from "express";
import * as http from "http"; import * as http from "http";
import { config } from "../../../config"; import { config } from "../../../config";
@ -225,7 +226,7 @@ const handleUpstreamErrors: ProxyResHandlerWithBody = async (
break; break;
case "anthropic": case "anthropic":
case "aws": case "aws":
await handleAnthropicBadRequestError(req, errorPayload); await handleAnthropicAwsBadRequestError(req, errorPayload);
break; break;
default: default:
assertNever(service); assertNever(service);
@ -247,7 +248,9 @@ const handleUpstreamErrors: ProxyResHandlerWithBody = async (
); );
keyPool.update(req.key!, { allowsMultimodality: false }); keyPool.update(req.key!, { allowsMultimodality: false });
await reenqueueRequest(req); await reenqueueRequest(req);
throw new RetryableError("Claude request re-enqueued because key does not support multimodality."); throw new RetryableError(
"Claude request re-enqueued because key does not support multimodality."
);
} else { } else {
keyPool.disable(req.key!, "revoked"); keyPool.disable(req.key!, "revoked");
errorPayload.proxy_note = `Assigned API key is invalid or revoked, please try again.`; errorPayload.proxy_note = `Assigned API key is invalid or revoked, please try again.`;
@ -347,7 +350,7 @@ const handleUpstreamErrors: ProxyResHandlerWithBody = async (
throw new HttpError(statusCode, errorPayload.error?.message); throw new HttpError(statusCode, errorPayload.error?.message);
}; };
async function handleAnthropicBadRequestError( async function handleAnthropicAwsBadRequestError(
req: Request, req: Request,
errorPayload: ProxiedErrorPayload errorPayload: ProxiedErrorPayload
) { ) {
@ -382,11 +385,13 @@ async function handleAnthropicBadRequestError(
return; return;
} }
const isDisabled = error?.message?.match(/organization has been disabled/i); const isDisabled =
error?.message?.match(/organization has been disabled/i) ||
error?.message?.match(/^operation not allowed/i);
if (isDisabled) { if (isDisabled) {
req.log.warn( req.log.warn(
{ key: req.key?.hash, message: error?.message }, { key: req.key?.hash, message: error?.message },
"Anthropic key has been disabled." "Anthropic/AWS key has been disabled."
); );
keyPool.disable(req.key!, "revoked"); keyPool.disable(req.key!, "revoked");
errorPayload.proxy_note = `Assigned key has been disabled. (${error?.message})`; errorPayload.proxy_note = `Assigned key has been disabled. (${error?.message})`;
@ -512,7 +517,7 @@ async function handleOpenAIRateLimitError(
// keyPool.markRateLimited(req.key!); // keyPool.markRateLimited(req.key!);
// break; // break;
default: default:
errorPayload.proxy_note = `This is likely a temporary error with OpenAI. Try again in a few seconds.`; errorPayload.proxy_note = `This is likely a temporary error with the API. Try again in a few seconds.`;
break; break;
} }
return errorPayload; return errorPayload;