mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
@@ -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(),
|
||||
|
||||
65
tests/unit/provider-apikey-cap-6715.test.ts
Normal file
65
tests/unit/provider-apikey-cap-6715.test.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user