mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +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.
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import { updateProviderConnection } from "@/lib/db/providers";
|
|
import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin";
|
|
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/errorSanitization.ts";
|
|
|
|
type Patch = {
|
|
testStatus: string;
|
|
isActive?: boolean;
|
|
lastError?: string | null;
|
|
errorCode?: string | null;
|
|
lastErrorType?: string | null;
|
|
lastErrorAt?: string | null;
|
|
};
|
|
const TERMINAL = new Set(["banned", "expired", "deactivated", "credits_exhausted"]);
|
|
|
|
export async function writeTerminalStatus(
|
|
connectionId: string,
|
|
patch: Patch,
|
|
origin: "probe" | "production"
|
|
): Promise<void> {
|
|
const isTerminal = TERMINAL.has(patch.testStatus.toLowerCase());
|
|
const persistedLastError =
|
|
patch.lastError == null
|
|
? null
|
|
: sanitizeErrorMessage(patch.lastError) || "Provider request failed";
|
|
// Double gate: AsyncLocalStorage probe + explicit origin "probe" — fail-safe ON
|
|
const probeIsolated = await shouldIsolateProbeFailures();
|
|
if ((origin === "probe" || probeIsolated) && isTerminal) {
|
|
// record-only: never remove from pool
|
|
await updateProviderConnection(connectionId, {
|
|
lastError: persistedLastError,
|
|
lastErrorAt: new Date().toISOString(),
|
|
lastErrorType: patch.lastErrorType ?? null,
|
|
errorCode: patch.errorCode ?? null,
|
|
});
|
|
return;
|
|
}
|
|
await updateProviderConnection(connectionId, {
|
|
isActive: patch.isActive ?? (isTerminal ? false : undefined),
|
|
testStatus: patch.testStatus,
|
|
lastError: persistedLastError,
|
|
lastErrorAt: new Date().toISOString(),
|
|
lastErrorType: patch.lastErrorType ?? null,
|
|
errorCode: patch.errorCode ?? null,
|
|
});
|
|
}
|