fix(api): raise provider apiKey cap for cookie-based web providers (#6715) (#6759)

This commit is contained in:
Ronaldo Davi
2026-07-10 15:06:09 -03:00
committed by GitHub
parent 6105bc1713
commit 23c4086a81
2 changed files with 79 additions and 3 deletions

View File

@@ -41,10 +41,21 @@ const providerNodeIconUrlSchema = z
})
.optional();
// #6715: the `apiKey` field is reused as the raw `Cookie:` header value for
// cookie-based web providers (Gemini Business, Copilot M365, ChatGPT Web,
// Claude Web, …). Real multi-cookie session headers (many `__Secure-*` entries,
// large session tokens) legitimately exceed the old 10,000-char cap, so saving
// a cookie that the provider's own `validate` check (validateProviderApiKeySchema,
// uncapped) had already accepted failed with HTTP 400 "Too big …<=10000". Raised
// to a still-bounded ceiling — well under the 10 MB default request-body limit and
// the unconstrained SQLite TEXT column — so garbage input is still rejected.
// Same fix shape as #6562 (priority cap raised to 100_000).
export const MAX_PROVIDER_CREDENTIAL_LENGTH = 100_000;
export const createProviderSchema = z
.object({
provider: z.string().min(1).max(100),
apiKey: z.string().max(10000).optional(),
apiKey: z.string().max(MAX_PROVIDER_CREDENTIAL_LENGTH).optional(),
name: z.string().min(1).max(200),
priority: z.number().int().min(1).max(100).optional(),
globalPriority: z.number().int().min(1).max(100).nullable().optional(),
@@ -91,7 +102,7 @@ export const bulkCreateProviderSchema = z
.array(
z.object({
name: z.string().min(1).max(200),
apiKey: z.string().min(1).max(10000),
apiKey: z.string().min(1).max(MAX_PROVIDER_CREDENTIAL_LENGTH),
// Per-key account id — required for cloudflare-ai (enforced in superRefine below).
accountId: z.string().min(1).max(200).optional(),
})
@@ -304,7 +315,7 @@ export const updateProviderConnectionSchema = z
globalPriority: z.union([z.coerce.number().int().min(1).max(100_000), z.null()]).optional(),
defaultModel: z.union([z.string().max(200), z.null()]).optional(),
isActive: z.boolean().optional(),
apiKey: z.string().max(10000).optional(),
apiKey: z.string().max(MAX_PROVIDER_CREDENTIAL_LENGTH).optional(),
testStatus: z.string().max(50).optional(),
lastError: z.union([z.string(), z.null()]).optional(),
lastErrorAt: z.union([z.string(), z.null()]).optional(),

View File

@@ -0,0 +1,65 @@
// Regression guard for #6715 — the provider connection `apiKey` field was
// hard-capped at 10,000 chars in the Zod save schemas:
// src/shared/validation/schemas/provider.ts
// createProviderSchema (add connection)
// bulkCreateProviderSchema (bulk import)
// updateProviderConnectionSchema (edit connection)
//
// That `apiKey` field is reused as the raw `Cookie:` header value for cookie-
// based web providers (Gemini Business, Copilot M365, ChatGPT Web, Claude Web,
// …). Real multi-cookie session headers (many `__Secure-*` entries, large
// session tokens) legitimately exceed 10,000 chars. The provider's own
// `validate` schema (validateProviderApiKeySchema) has NO cap, so the cookie
// validated as OK — then `save` rejected it with HTTP 400
// "Too big: expected string to have <=10000 characters".
//
// Fix: raise the ceiling to MAX_PROVIDER_CREDENTIAL_LENGTH (100_000) — still a
// sane anti-abuse bound (well under the 10 MB default request-body limit and
// unconstrained SQLite TEXT storage), just wide enough for real cookie headers.
// Same fix shape as #6562 (priority cap raised to 100_000).
import test from "node:test";
import assert from "node:assert/strict";
const { createProviderSchema, bulkCreateProviderSchema, updateProviderConnectionSchema } =
await import("../../src/shared/validation/schemas.ts");
// A realistic large cookie-header value: > 10_000 chars, < the new 100_000 cap.
const LARGE_COOKIE = "__Secure-session=" + "a".repeat(20_000);
// Beyond the new ceiling — must still be rejected (anti-abuse bound preserved).
const OVERSIZE_COOKIE = "x".repeat(100_001);
test("createProviderSchema accepts a >10000-char cookie apiKey (#6715)", () => {
const result = createProviderSchema.safeParse({
provider: "gemini-business",
name: "Gemini Business (cookie)",
apiKey: LARGE_COOKIE,
});
assert.equal(result.success, true, JSON.stringify(result.error?.issues));
});
test("bulkCreateProviderSchema accepts a >10000-char cookie apiKey (#6715)", () => {
const result = bulkCreateProviderSchema.safeParse({
provider: "gemini-business",
entries: [{ name: "cookie-1", apiKey: LARGE_COOKIE }],
});
assert.equal(result.success, true, JSON.stringify(result.error?.issues));
});
test("updateProviderConnectionSchema accepts a >10000-char cookie apiKey (#6715)", () => {
const result = updateProviderConnectionSchema.safeParse({ apiKey: LARGE_COOKIE });
assert.equal(result.success, true, JSON.stringify(result.error?.issues));
});
test("createProviderSchema still rejects an oversize apiKey past the new ceiling (control)", () => {
const result = createProviderSchema.safeParse({
provider: "gemini-business",
name: "too big",
apiKey: OVERSIZE_COOKIE,
});
assert.equal(result.success, false);
});
test("updateProviderConnectionSchema still rejects an oversize apiKey past the new ceiling (control)", () => {
const result = updateProviderConnectionSchema.safeParse({ apiKey: OVERSIZE_COOKIE });
assert.equal(result.success, false);
});