mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
* fix(providers): route AgentRouter key validation through CC wire image (#6377) * test(6377): type fetch mock to satisfy no-explicit-any gate
This commit is contained in:
committed by
GitHub
parent
49e0b7d667
commit
df3a6cf674
1
changelog.d/fixes/6377-6377-agentrouter-key.md
Normal file
1
changelog.d/fixes/6377-6377-agentrouter-key.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): route AgentRouter key validation through the CC wire image so a valid key no longer 403s as "Invalid API key" (#6377)
|
||||
@@ -17,6 +17,11 @@ import { MODAL_DEFAULT_VALIDATION_MODEL_ID } from "@/shared/constants/modal";
|
||||
import { validateQoderCliPat } from "@omniroute/open-sse/services/qoderCli.ts";
|
||||
import { validateImageProviderApiKey } from "@/lib/providers/imageValidation";
|
||||
import { KiroService } from "@/lib/oauth/services/kiro";
|
||||
import { usesCcWireImage } from "@omniroute/open-sse/services/ccWireImageBuiltins.ts";
|
||||
import {
|
||||
buildProviderHeaders,
|
||||
buildProviderUrl,
|
||||
} from "@omniroute/open-sse/services/provider.ts";
|
||||
|
||||
import {
|
||||
OPENAI_LIKE_FORMATS,
|
||||
@@ -825,6 +830,27 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
}
|
||||
|
||||
if (entry.format === "claude") {
|
||||
// Built-in CC-wire-image providers (e.g. agentrouter, #6056/#6255) gate
|
||||
// their WAF on the dynamic Claude-Code fingerprint (User-Agent,
|
||||
// `?beta=true` chat path, anthropic-beta/x-app/X-Stainless-* headers).
|
||||
// The real chat-request path already routes through
|
||||
// buildProviderUrl/buildProviderHeaders for this; the validation probe
|
||||
// must use the SAME wire image or a genuinely valid key gets 403'd as
|
||||
// "unauthorized client detected" (#6377).
|
||||
if (usesCcWireImage(provider)) {
|
||||
const requestBaseUrl = buildProviderUrl(provider, modelId, true, { baseUrl });
|
||||
const requestHeaders = buildProviderHeaders(provider, { apiKey }, true);
|
||||
|
||||
return await validateAnthropicLikeProvider({
|
||||
apiKey,
|
||||
baseUrl: requestBaseUrl,
|
||||
modelId,
|
||||
headers: requestHeaders,
|
||||
providerSpecificData,
|
||||
isLocal,
|
||||
});
|
||||
}
|
||||
|
||||
const requestBaseUrl = `${baseUrl}${entry.urlSuffix || ""}`;
|
||||
const requestHeaders = {
|
||||
...(entry.headers || {}),
|
||||
|
||||
81
tests/unit/repro-6377-agentrouter-validation.test.ts
Normal file
81
tests/unit/repro-6377-agentrouter-validation.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// Repro for GitHub issue #6377: AgentRouter "Check" (validate API key) returns
|
||||
// "Invalid API key" for a genuinely valid token.
|
||||
//
|
||||
// Root cause: PR #6255 (#6056) routed the REAL chat-request path for the
|
||||
// built-in `agentrouter` provider through the dynamic Claude-Code wire image
|
||||
// (buildProviderHeaders/buildProviderUrl -> CC fingerprint headers + the
|
||||
// `?beta=true` chat path) specifically because AgentRouter's WAF rejects
|
||||
// requests that don't look like the official Claude Code client
|
||||
// ("unauthorized client detected" — see the comment in
|
||||
// open-sse/config/providers/registry/agentrouter/index.ts).
|
||||
//
|
||||
// validation.ts's generic `entry.format === "claude"` branch (used for the
|
||||
// dashboard "Check" button) was NOT updated by that PR: it still builds a
|
||||
// bare request (Content-Type + x-api-key + anthropic-version only, no
|
||||
// `?beta=true`, no CC fingerprint headers) straight to entry.baseUrl. A WAF
|
||||
// that gates on the CC wire image will legitimately 403 this validation
|
||||
// probe even though the same key works for real chat traffic — the exact
|
||||
// mismatch the reporter describes.
|
||||
//
|
||||
// This test simulates that WAF: accept only requests that carry the CC wire
|
||||
// image markers (User-Agent: claude-cli/... and the `?beta=true` chat path);
|
||||
// reject everything else with 403 "unauthorized client detected", mapped by
|
||||
// the validator to { valid: false, error: "Invalid API key" }.
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
test.afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
test("agentrouter key validation must not false-negative behind the CC-wire-image WAF gate (#6377)", async () => {
|
||||
const calls: { url: string; headers: Record<string, string> }[] = [];
|
||||
|
||||
globalThis.fetch = async (url: string | URL, init: RequestInit = {}) => {
|
||||
const u = String(url);
|
||||
const headers: Record<string, string> = {};
|
||||
if (init?.headers) {
|
||||
for (const [k, v] of Object.entries(init.headers)) {
|
||||
headers[k.toLowerCase()] = String(v);
|
||||
}
|
||||
}
|
||||
calls.push({ url: u, headers });
|
||||
|
||||
// Emulate AgentRouter's real-world WAF: only requests that look like the
|
||||
// official Claude Code client (CC wire image: `?beta=true` chat path +
|
||||
// a claude-cli User-Agent) are let through with a valid key. Anything
|
||||
// else — even with the SAME valid key — is rejected as an
|
||||
// "unauthorized client".
|
||||
const looksLikeClaudeCode =
|
||||
u.includes("beta=true") && /claude-cli/i.test(headers["user-agent"] || "");
|
||||
|
||||
if (!looksLikeClaudeCode) {
|
||||
return new Response(JSON.stringify({ error: "unauthorized client detected" }), {
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ id: "msg_ok" }), { status: 200 });
|
||||
};
|
||||
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "agentrouter",
|
||||
apiKey: "sk-genuinely-valid-agentrouter-key",
|
||||
providerSpecificData: {},
|
||||
});
|
||||
|
||||
// BEFORE the fix: validation.ts's generic `entry.format === "claude"`
|
||||
// branch sends a bare request (no `?beta=true`, no CC User-Agent) and gets
|
||||
// 403'd by the WAF -> false "Invalid API key" for a key that actually works.
|
||||
assert.equal(
|
||||
result.valid,
|
||||
true,
|
||||
`expected the valid key to validate, got: ${JSON.stringify(result)} — ` +
|
||||
`requests made: ${JSON.stringify(calls.map((c) => ({ url: c.url, ua: c.headers["user-agent"] })))}`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user