From 77d6a415d139b0f55a42e8c9d86fc9ddf87974b0 Mon Sep 17 00:00:00 2001 From: cita Date: Fri, 20 Mar 2026 00:39:39 +0800 Subject: [PATCH] fix managed downstream deny-all semantics --- src/server/proxy-core/surfaces/geminiSurface.ts | 4 ++-- src/server/routes/proxy/downstreamPolicy.ts | 8 +------- src/server/services/downstreamApiKeyService.test.ts | 1 + src/server/services/downstreamApiKeyService.ts | 3 ++- src/server/services/downstreamPolicyTypes.ts | 7 +------ src/server/services/tokenRouter.downstream-policy.test.ts | 1 + src/server/services/tokenRouter.ts | 2 +- 7 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/server/proxy-core/surfaces/geminiSurface.ts b/src/server/proxy-core/surfaces/geminiSurface.ts index fdac3d3..39e1c4e 100644 --- a/src/server/proxy-core/surfaces/geminiSurface.ts +++ b/src/server/proxy-core/surfaces/geminiSurface.ts @@ -114,10 +114,10 @@ function resolveUpstreamPath(apiVersion: string, modelActionPath: string): strin return `/${normalizedVersion}/${normalizedAction}`; } -function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown }): boolean { +function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown; denyAllWhenEmpty?: unknown }): boolean { const supportedModels = Array.isArray(policy.supportedModels) ? policy.supportedModels : []; const allowedRouteIds = Array.isArray(policy.allowedRouteIds) ? policy.allowedRouteIds : []; - return supportedModels.length > 0 || allowedRouteIds.length > 0; + return supportedModels.length > 0 || allowedRouteIds.length > 0 || policy.denyAllWhenEmpty === true; } function extractGeminiListedModelName(item: unknown): string { diff --git a/src/server/routes/proxy/downstreamPolicy.ts b/src/server/routes/proxy/downstreamPolicy.ts index 090e1ab..4863810 100644 --- a/src/server/routes/proxy/downstreamPolicy.ts +++ b/src/server/routes/proxy/downstreamPolicy.ts @@ -1,16 +1,11 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; import { getProxyAuthContext } from '../../middleware/auth.js'; import { isModelAllowedByPolicyOrAllowedRoutes, recordManagedKeyCostUsage } from '../../services/downstreamApiKeyService.js'; -import { - EMPTY_DOWNSTREAM_ROUTING_POLICY, - GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY, - type DownstreamRoutingPolicy, -} from '../../services/downstreamPolicyTypes.js'; +import { EMPTY_DOWNSTREAM_ROUTING_POLICY, type DownstreamRoutingPolicy } from '../../services/downstreamPolicyTypes.js'; export function getDownstreamRoutingPolicy(request: FastifyRequest): DownstreamRoutingPolicy { const authContext = getProxyAuthContext(request); if (!authContext) return EMPTY_DOWNSTREAM_ROUTING_POLICY; - if (authContext.keyId === null) return GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY; return authContext.policy; } @@ -21,7 +16,6 @@ export async function ensureModelAllowedForDownstreamKey( ): Promise { const authContext = getProxyAuthContext(request); if (!authContext) return true; - if (authContext.keyId === null) return true; if (await isModelAllowedByPolicyOrAllowedRoutes(requestedModel, authContext.policy)) { return true; diff --git a/src/server/services/downstreamApiKeyService.test.ts b/src/server/services/downstreamApiKeyService.test.ts index 843f20f..faa08e8 100644 --- a/src/server/services/downstreamApiKeyService.test.ts +++ b/src/server/services/downstreamApiKeyService.test.ts @@ -141,6 +141,7 @@ describe('downstreamApiKeyService', () => { supportedModels: [], allowedRouteIds: [], siteWeightMultipliers: {}, + denyAllWhenEmpty: true, }; expect(await service.isModelAllowedByPolicyOrAllowedRoutes('gpt-4o-mini', policy)).toBe(false); diff --git a/src/server/services/downstreamApiKeyService.ts b/src/server/services/downstreamApiKeyService.ts index e7a99d3..9db2e41 100644 --- a/src/server/services/downstreamApiKeyService.ts +++ b/src/server/services/downstreamApiKeyService.ts @@ -234,7 +234,7 @@ export async function isModelAllowedByPolicyOrAllowedRoutes(model: string, polic const hasPatternRules = patterns.length > 0; const hasRouteRules = allowedRouteIds.length > 0; - if (!hasPatternRules && !hasRouteRules) return false; + if (!hasPatternRules && !hasRouteRules) return policy.denyAllWhenEmpty === true ? false : true; if (hasPatternRules && patterns.some((pattern) => matchesDownstreamModelPattern(model, pattern))) { return true; @@ -278,6 +278,7 @@ export function toPolicyFromView(view: Pick; + denyAllWhenEmpty?: boolean; } export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = { @@ -9,9 +10,3 @@ export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = { allowedRouteIds: [], siteWeightMultipliers: {}, }; - -export const GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = { - supportedModels: ['*'], - allowedRouteIds: [], - siteWeightMultipliers: {}, -}; diff --git a/src/server/services/tokenRouter.downstream-policy.test.ts b/src/server/services/tokenRouter.downstream-policy.test.ts index 4d174f9..c394a5f 100644 --- a/src/server/services/tokenRouter.downstream-policy.test.ts +++ b/src/server/services/tokenRouter.downstream-policy.test.ts @@ -137,6 +137,7 @@ describe('TokenRouter downstream policy', () => { allowedRouteIds: [], supportedModels: [], siteWeightMultipliers: {}, + denyAllWhenEmpty: true, }); expect(deniedPick).toBeNull(); diff --git a/src/server/services/tokenRouter.ts b/src/server/services/tokenRouter.ts index e0ba772..4b3c5fc 100644 --- a/src/server/services/tokenRouter.ts +++ b/src/server/services/tokenRouter.ts @@ -537,7 +537,7 @@ function isModelAllowedByDownstreamPolicy(requestedModel: string, policy: Downst : []; const hasSupportedPatterns = supportedPatterns.length > 0; const hasAllowedRoutes = policy.allowedRouteIds.length > 0; - if (!hasSupportedPatterns && !hasAllowedRoutes) return false; + if (!hasSupportedPatterns && !hasAllowedRoutes) return policy.denyAllWhenEmpty === true ? false : true; const matchedSupportedPattern = supportedPatterns.some((pattern) => matchesModelPattern(requestedModel, pattern)); if (matchedSupportedPattern) return true; if (hasAllowedRoutes) return true;