Skip to content

Commit 32b8c89

Browse files
committed
fix(access-control): only gate a model field the provider allowlist is about
The seed gate ran `isModelUsable` on every subblock named `model`, but `getProviderFromModel` resolves chat models and falls back to `ollama` for everything else. 28 of the 44 seeded model defaults in the registry are embedding, speech, image, video or search ids — so for any group with a provider allowlist that omits Ollama, those blocks were created with an empty model. Adds `findProviderFromModel`, the non-guessing half of `getProviderFromModel`, which returns `null` where the registry declares nothing. `isModelUsable` now treats an unresolved id as not-a-provider-choice and leaves it alone, matching the rule the operation gate already follows: never guess, and let the server stay authoritative. `getProviderFromModel` delegates to it, so there is one resolution path and its ollama fallback is unchanged. This also repairs the same misjudgement where it predates the branch: the model combobox filtered its options through the identical provider check, so those 28 defaults were already being hidden from their own pickers for allowlisted groups. The dead `try/catch` around the old call went with it — `getProviderFromModel` returns a fallback rather than throwing for an unknown id.
1 parent 75face5 commit 32b8c89

3 files changed

Lines changed: 56 additions & 23 deletions

File tree

apps/sim/hooks/use-permission-config.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { useOptionalWorkspaceHostContext } from '@/app/workspace/[workspaceId]/p
2424
import { useCustomBlockOverlayVersion } from '@/blocks/custom/client-overlay'
2525
import { overlayVisibility } from '@/blocks/visibility/context'
2626
import { useUserPermissionConfig } from '@/ee/access-control/hooks/permission-groups'
27-
import { getProviderFromModel } from '@/providers/utils'
27+
import { findProviderFromModel } from '@/providers/utils'
2828

2929
export interface PermissionConfigResult {
3030
config: PermissionGroupConfig
@@ -138,13 +138,13 @@ export function usePermissionConfig(): PermissionConfigResult {
138138
const isModelUsable = useMemo(() => {
139139
return (model: string) => {
140140
if (!isModelAllowed(model)) return false
141-
try {
142-
return isProviderAllowed(getProviderFromModel(model))
143-
} catch {
144-
/* A model whose provider cannot be derived is left to the server gate
145-
rather than hidden on a parse failure. */
146-
return true
147-
}
141+
const providerId = findProviderFromModel(model)
142+
/* Only chat models resolve to a provider. A `model` field holding an
143+
embedding, speech, image or video id is not a provider choice, so the
144+
provider allowlist has nothing to say about it — judging it anyway
145+
would read every such id as Ollama and reject it. */
146+
if (!providerId) return true
147+
return isProviderAllowed(providerId)
148148
}
149149
}, [isModelAllowed, isProviderAllowed])
150150

apps/sim/providers/utils.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
describeModelLevel,
1717
extractAndParseJSON,
1818
filterBlacklistedModels,
19+
findProviderFromModel,
1920
formatCost,
2021
generateStructuredOutputInstructions,
2122
getAllModelProviders,
@@ -2045,3 +2046,27 @@ describe('describeModelLevel', () => {
20452046
expect(describeModelLevel('')).toBe('(unset)')
20462047
})
20472048
})
2049+
2050+
describe('findProviderFromModel', () => {
2051+
it('resolves a chat model to its declaring provider', () => {
2052+
expect(findProviderFromModel('claude-sonnet-5')).toBe('anthropic')
2053+
expect(findProviderFromModel('gpt-5.2')).toBe('openai')
2054+
})
2055+
2056+
it('is case-insensitive, like getProviderFromModel', () => {
2057+
expect(findProviderFromModel('Claude-Sonnet-5')).toBe('anthropic')
2058+
})
2059+
2060+
it('returns null for ids the registry does not declare, instead of guessing ollama', () => {
2061+
/* The registry holds chat models only. Speech, image, video and embedding
2062+
ids reach `model` subblocks too, and a permission gate must not read them
2063+
as Ollama models — see isModelUsable. */
2064+
for (const id of ['whisper-1', 'dall-e-3', 'veo-3.1', 'embed-v4.0', 'tts-1']) {
2065+
expect(findProviderFromModel(id)).toBeNull()
2066+
}
2067+
})
2068+
2069+
it('still lets getProviderFromModel fall back to ollama for those ids', () => {
2070+
expect(getProviderFromModel('whisper-1')).toBe('ollama')
2071+
})
2072+
})

apps/sim/providers/utils.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -286,27 +286,35 @@ export function getAllModelProviders(): Record<string, ProviderId> {
286286
)
287287
}
288288

289-
export function getProviderFromModel(model: string): ProviderId {
289+
/**
290+
* The provider that declares `model`, or `null` when none does.
291+
*
292+
* The non-guessing half of {@link getProviderFromModel}. A caller that *gates*
293+
* on the answer needs "unknown" to stay distinct from "ollama": this registry
294+
* holds chat models only, so every embedding, speech, image and video model id
295+
* would otherwise read as an Ollama model and be judged against an allowlist
296+
* that was never about it.
297+
*/
298+
export function findProviderFromModel(model: string): ProviderId | null {
290299
const normalizedModel = model.toLowerCase()
291300

292-
let providerId: ProviderId | null = null
301+
const declared = getAllModelProviders()[normalizedModel]
302+
if (declared) return declared
293303

294-
if (normalizedModel in getAllModelProviders()) {
295-
providerId = getAllModelProviders()[normalizedModel]
296-
} else {
297-
for (const [id, config] of Object.entries(providers)) {
298-
if (config.modelPatterns) {
299-
for (const pattern of config.modelPatterns) {
300-
if (pattern.test(normalizedModel)) {
301-
providerId = id as ProviderId
302-
break
303-
}
304-
}
305-
}
306-
if (providerId) break
304+
for (const [id, config] of Object.entries(providers)) {
305+
for (const pattern of config.modelPatterns ?? []) {
306+
if (pattern.test(normalizedModel)) return id as ProviderId
307307
}
308308
}
309309

310+
return null
311+
}
312+
313+
export function getProviderFromModel(model: string): ProviderId {
314+
const normalizedModel = model.toLowerCase()
315+
316+
let providerId = findProviderFromModel(model)
317+
310318
if (!providerId) {
311319
logger.warn(`No provider found for model: ${model}, defaulting to ollama`)
312320
providerId = 'ollama'

0 commit comments

Comments
 (0)