mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 03:12:36 +03:00
Validado sobre o tip de `release/v3.8.51` depois de reconciliar com o #12620, que entrou primeiro nesta mesma sessão e ataca a mesma classe de problema por outra arquitetura. **A colisão e como foi resolvida.** O #12620 consertou o GHSA-qv45-56jc-4wmj adicionando `RAW_CREDENTIAL_PATTERNS` a `error.ts` e importando-os em `upstreamErrorPassthrough.ts`. Este PR resolve o mesmo problema quebrando `error.ts` em `errorSanitization.ts` + `errorPathRedaction.ts`. Mantive a divisão em módulos deste PR, porque ao comparar os dois vocabulários o dele já era mais amplo: o `STRONG_CREDENTIAL_TOKEN` daqui cobre `sk-`/`sk_` **com lookbehind e uma variante para a forma embutida** (que pega `sk-proj-…`), mais Slack `xox-`, AWS `AKIA`/`ASIA`, `github_pat_`/`ghp_`/`glpat-` e JWT de três segmentos. A única forma que o #12620 carregava e este conjunto não tinha era a chave do Google (`AIza…`) — adicionada aqui, com o mesmo quantificador limitado que os irmãos usam (AGENTS.md → PII §1, já que isso roda sobre corpos upstream não confiáveis). **A verificação não foi por inspeção.** Rodei as suítes do próprio #12620 contra esta estrutura: **48/48** em `error-sanitizer-sk-key-qv45`, `bifrost-relay-response-leak-9m72`, `search-baseurl-client-override-3f8g` e `search-baseurl-ssrf-guard` — incluindo a asserção anti-drift daquela suíte, que é o oráculo certo aqui: *para todo corpo que a camada de passthrough recusa como vazante, o sanitizador de fallback não pode devolvê-lo intacto*. Ela passa, então a propriedade de segurança dos três GHSAs sobrevive à troca de arquitetura. Os 21 arquivos de teste deste PR: **259/259**. `typecheck:core` limpo.
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { buildErrorBody } from "./error.ts";
|
|
import type { StreamFailurePayload } from "./streamErrorFormat.ts";
|
|
import type { StreamTiming } from "./streamTiming.ts";
|
|
|
|
type CompletePayload = {
|
|
status: number;
|
|
usage: unknown;
|
|
responseBody: unknown;
|
|
providerPayload: unknown;
|
|
clientPayload: unknown;
|
|
error: string;
|
|
errorCode?: string;
|
|
ttft: number | null;
|
|
itlMs: number | null;
|
|
interrupted: boolean;
|
|
};
|
|
|
|
type AborterContext = {
|
|
onFailure?: ((payload: StreamFailurePayload) => boolean | void | Promise<void>) | null;
|
|
onComplete?: ((payload: CompletePayload) => void) | null;
|
|
getUsage: () => unknown;
|
|
timing: StreamTiming;
|
|
buildProviderPayload: () => unknown;
|
|
buildClientPayload: (body: unknown) => unknown;
|
|
clearIdleTimer: () => void;
|
|
clearPendingRequest: () => void;
|
|
markPendingRequestCleared: (error: Error) => Error;
|
|
model?: string | null;
|
|
};
|
|
|
|
export function createStreamFailureAborter(context: AborterContext) {
|
|
return (
|
|
controller: TransformStreamDefaultController<Uint8Array>,
|
|
failure: StreamFailurePayload,
|
|
publicMessage: string,
|
|
options: { notifyComplete?: boolean } = {}
|
|
): void => {
|
|
let handled = false;
|
|
context.timing.markInterrupted();
|
|
if (context.onFailure) {
|
|
try {
|
|
handled = context.onFailure(failure) === true;
|
|
} catch (error) {
|
|
console.debug("[STREAM] onFailure callback error:", error);
|
|
}
|
|
}
|
|
let safeMessage = publicMessage || "Upstream failure";
|
|
if (options.notifyComplete && context.onComplete) {
|
|
const body = buildErrorBody(failure.status, failure.message);
|
|
safeMessage = body.error.message;
|
|
try {
|
|
context.onComplete({
|
|
status: failure.status,
|
|
usage: context.getUsage(),
|
|
responseBody: body,
|
|
ttft: context.timing.ttftMs(),
|
|
itlMs: context.timing.avgItlMs(),
|
|
interrupted: context.timing.interrupted,
|
|
error: safeMessage,
|
|
errorCode: failure.code,
|
|
providerPayload: context.buildProviderPayload(),
|
|
clientPayload: context.buildClientPayload(body),
|
|
});
|
|
handled = true;
|
|
} catch (error) {
|
|
console.debug(
|
|
`[STREAM] onComplete callback error in error path (${context.model || "unknown"}):`,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
context.clearIdleTimer();
|
|
if (!handled) context.clearPendingRequest();
|
|
controller.error(context.markPendingRequestCleared(new Error(safeMessage)));
|
|
};
|
|
}
|