From 5dc8631fe47fdc4f349ab59c72c59b15f43590c6 Mon Sep 17 00:00:00 2001 From: Austin Liu <193228693+Dingding-leo@users.noreply.github.com> Date: Thu, 6 Aug 2026 18:35:39 +0930 Subject: [PATCH] [v3.8.50] fix(db/apiKeys): respect provider parameter in group model permission checks (fixes #8803) (#8817) Validated in local merge-train T7 (ungrouped batch 2) --- src/lib/db/apiKeys.ts | 32 ++++++-------- tests/unit/group-provider-permission.test.ts | 44 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 tests/unit/group-provider-permission.test.ts diff --git a/src/lib/db/apiKeys.ts b/src/lib/db/apiKeys.ts index 2fd4114622..0b43e699a9 100644 --- a/src/lib/db/apiKeys.ts +++ b/src/lib/db/apiKeys.ts @@ -1460,28 +1460,22 @@ export async function isModelAllowedForKey( } } - // Empty array means all models allowed - if (!allowedModels || allowedModels.length === 0) { - return true; - } - - let allowed = false; - - // Check if model matches each allowed pattern // Support exact match and prefix match (e.g., "openai/*" allows all OpenAI models) - for (const pattern of allowedModels) { - if (modelPatternMatches(pattern, modelPermissionCandidates)) { - allowed = true; - break; - } - } + let allowed = + !allowedModels || + allowedModels.length === 0 || + allowedModels.some((pattern) => modelPatternMatches(pattern, modelPermissionCandidates)); - // If key belongs to groups, also check group-level permissions + // Extract model target and optional provider prefix if present (e.g. "openai/gpt-4" -> modelTarget: "gpt-4", provider: "openai") + const hasProviderPrefix = modelId?.includes("/"); + const provider = hasProviderPrefix ? modelId.split("/")[0] : undefined; + const modelTarget = hasProviderPrefix ? modelId.split("/").slice(1).join("/") : modelId || ""; + + // If key belongs to groups, check both modelTarget and full modelId against group rules if (metadata.id) { - const groupAccess = checkKeyModelAccess(metadata.id, modelId || ""); - if (!groupAccess.allowed) { - allowed = false; - } + const targetOk = checkKeyModelAccess(metadata.id, modelTarget, provider).allowed; + const fullOk = checkKeyModelAccess(metadata.id, modelId || "", provider).allowed; + if (!targetOk || !fullOk) allowed = false; } // Cache the result if (!usesSettingDependentClaudeRouting) { diff --git a/tests/unit/group-provider-permission.test.ts b/tests/unit/group-provider-permission.test.ts new file mode 100644 index 0000000000..f1a47137c7 --- /dev/null +++ b/tests/unit/group-provider-permission.test.ts @@ -0,0 +1,44 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +process.env.API_KEY_SECRET = "test-secret-key-for-unit-tests-123456789"; + +import * as apiKeys from "../../src/lib/db/apiKeys"; +import * as apiKeyGroups from "../../src/lib/db/apiKeyGroups"; + +test("isModelAllowedForKey respects provider parameter in checkKeyModelAccess", async () => { + const createdKey = await apiKeys.createApiKey( + "Group Provider Key", + "test-machine-group-provider" + ); + assert.ok(createdKey); + + const group = apiKeyGroups.createKeyGroup("Provider Test Group", "Testing provider param"); + assert.ok(group); + + apiKeyGroups.addKeyToGroup(createdKey.id, group.id); + + apiKeyGroups.addGroupPermission(group.id, "gpt-4*", "deny", "openai"); + apiKeyGroups.addGroupPermission(group.id, "*", "allow"); + + const res1 = apiKeyGroups.checkKeyModelAccess(createdKey.id, "gpt-4", "openai"); + console.log("checkKeyModelAccess openai/gpt-4:", res1); + + const res2 = apiKeyGroups.checkKeyModelAccess(createdKey.id, "gpt-4", "anthropic"); + console.log("checkKeyModelAccess anthropic/gpt-4:", res2); + + // Model with provider "openai" matching pattern "gpt-4*" should be denied + const allowedOpenAIDenied = await apiKeys.isModelAllowedForKey(createdKey.key, "openai/gpt-4"); + assert.equal( + allowedOpenAIDenied, + false, + "openai/gpt-4 should be denied by provider-specific rule" + ); + + // Model with provider "anthropic" matching pattern "gpt-4*" should NOT trigger the openai-specific deny rule + const allowedAnthropicAllowed = await apiKeys.isModelAllowedForKey( + createdKey.key, + "anthropic/gpt-4" + ); + assert.equal(allowedAnthropicAllowed, true, "anthropic/gpt-4 should be allowed"); +});