fix(providers): route AgentRouter key validation through CC wire image (#6377) (#6882)

* 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:
Diego Rodrigues de Sa e Souza
2026-07-12 02:00:19 -03:00
committed by GitHub
parent 49e0b7d667
commit df3a6cf674
3 changed files with 108 additions and 0 deletions

View 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)

View File

@@ -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 || {}),

View 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"] })))}`
);
});