[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)
This commit is contained in:
Austin Liu
2026-08-06 18:35:39 +09:30
committed by GitHub
parent c3ae5b8893
commit 5dc8631fe4
2 changed files with 57 additions and 19 deletions

View File

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

View File

@@ -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");
});