fix(db): validate HuggingFace tokens via whoami-v2 auth probe (#4819)

Integrated into release/v3.8.37 — defining commit re-homed onto the god-file-split validation module (validateHuggingFaceProvider in validation/openaiFormat.ts + map wiring); 115/115 green.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-25 23:39:43 -03:00
committed by GitHub
parent 7193b5fa13
commit 86e487bdb3
4 changed files with 82 additions and 0 deletions

View File

@@ -10,6 +10,8 @@ _In development — bullets added per PR; finalized at release._
### 🔧 Bug Fixes
- **huggingface**: validate API tokens via the `whoami-v2` endpoint as a pure auth probe so fine-grained Inference-Provider tokens (valid even when model/task endpoints reject them) are no longer falsely marked invalid; only 401/403 means an invalid key, other non-OK statuses surface as transient upstream errors. (thanks @Delcado19)
- **sse/kiro**: reject the Anthropic-only `[1m]` context-1m suffix in `buildKiroPayload` before it reaches AWS Bedrock — Kiro is Bedrock-backed and cannot honor the beta, so a forwarded `kr/*[1m]` model id was malformed upstream; callers now get a clear error pointing them at a direct-Anthropic provider for 1M-context routing (thanks @Delcado19).
- **Engine Combos editor: saving a pipeline no longer fails silently with HTTP 400 (#4955).** The

View File

@@ -95,6 +95,7 @@ import {
validateOpenAILikeProvider,
validateCommandCodeProvider,
validateGeminiLikeProvider,
validateHuggingFaceProvider,
validateOpenAICompatibleProvider,
} from "./validation/openaiFormat";
import {
@@ -280,6 +281,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
}
},
"command-code": validateCommandCodeProvider,
huggingface: validateHuggingFaceProvider,
deepgram: validateDeepgramProvider,
assemblyai: validateAssemblyAIProvider,
"fal-ai": ({ apiKey, providerSpecificData }: any) =>

View File

@@ -227,6 +227,35 @@ export async function validateCommandCodeProvider({ apiKey, providerSpecificData
}
// HuggingFace fine-grained Inference-Provider tokens are valid even when
// model/task endpoints reject them, so the generic OpenAI-like probe against
// router.huggingface.co/v1/models falsely marks them invalid. Validate the
// token strictly as an auth check via the whoami-v2 endpoint instead: only
// 401/403 means the token is invalid; any other non-OK status is a transient
// upstream failure, NOT an invalid key.
export async function validateHuggingFaceProvider({ apiKey }: any) {
try {
const response = await validationRead("https://huggingface.co/api/whoami-v2", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.ok) {
return { valid: true, error: null, method: "huggingface_whoami" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
// Non-auth, non-OK status — surface as a transient upstream failure rather
// than declaring the (potentially valid) fine-grained token invalid.
return { valid: false, error: `HuggingFace token check returned ${response.status}` };
} catch (error: unknown) {
return toValidationErrorResult(error);
}
}
export async function validateGeminiLikeProvider({
apiKey,
baseUrl,

View File

@@ -2749,3 +2749,52 @@ test("isSecurityBlockError: a URL-guard block remains a security block", () => {
});
assert.equal(isSecurityBlockError(guardBlock), true);
});
// ─── huggingface validator (whoami-v2 auth probe) ────────────────────────────
// Fine-grained HF Inference-Provider tokens are valid even when model/task
// endpoints reject them. The validator must probe whoami-v2 as a pure auth
// check: only 401/403 is invalid; any other non-OK status is transient.
test("huggingface validator accepts a token whoami-v2 recognizes", async () => {
const calls: { url: string; headers: Record<string, string> }[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), headers: toPlainHeaders(init.headers) });
return new Response(JSON.stringify({ name: "hf-user", auth: { type: "access_token" } }), {
status: 200,
});
};
const result = await validateProviderApiKey({ provider: "huggingface", apiKey: "hf_validtoken" });
assert.equal(result.valid, true);
assert.equal(result.error, null);
assert.equal(result.method, "huggingface_whoami");
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://huggingface.co/api/whoami-v2");
assert.equal(calls[0].headers.Authorization, "Bearer hf_validtoken");
});
test("huggingface validator treats 401/403 as an invalid token", async () => {
globalThis.fetch = async () => new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
const unauthorized = await validateProviderApiKey({ provider: "huggingface", apiKey: "hf_bad" });
assert.equal(unauthorized.valid, false);
assert.equal(unauthorized.error, "Invalid API key");
globalThis.fetch = async () => new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 });
const forbidden = await validateProviderApiKey({ provider: "huggingface", apiKey: "hf_bad" });
assert.equal(forbidden.valid, false);
assert.equal(forbidden.error, "Invalid API key");
});
test("huggingface validator does NOT mark a fine-grained token invalid on a non-auth status", async () => {
// This is the false-negative the port fixes: a 503/404 from a model/task
// probe used to read as "invalid key". whoami-v2 returning a non-auth,
// non-OK status must surface as a transient error, never "Invalid API key".
globalThis.fetch = async () => new Response("upstream down", { status: 503 });
const result = await validateProviderApiKey({ provider: "huggingface", apiKey: "hf_finegrained" });
assert.equal(result.valid, false);
assert.notEqual(result.error, "Invalid API key");
assert.match(result.error || "", /HuggingFace token check returned 503/);
});