Skip to content

Commit c95aa87

Browse files
authored
feat(access-control): add per-model denylist to permission groups (#4794)
* feat(access-control): add per-model denylist to permission groups * fix(access-control): default deniedModels in response schema, hide blocked badge on disabled rows, trim comments * chore(access-control): reuse canonical DYNAMIC_MODEL_PROVIDERS from providers/models
1 parent 8daca91 commit c95aa87

9 files changed

Lines changed: 411 additions & 56 deletions

File tree

apps/sim/app/api/guardrails/validate/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { validatePII } from '@/lib/guardrails/validate_pii'
1212
import { validateRegex } from '@/lib/guardrails/validate_regex'
1313
import {
1414
assertPermissionsAllowed,
15+
ModelNotAllowedError,
1516
ProviderNotAllowedError,
1617
} from '@/ee/access-control/utils/permission-check'
1718

@@ -161,7 +162,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
161162
model,
162163
})
163164
} catch (err) {
164-
if (err instanceof ProviderNotAllowedError) {
165+
if (err instanceof ProviderNotAllowedError || err instanceof ModelNotAllowedError) {
165166
return NextResponse.json({
166167
success: true,
167168
output: {

apps/sim/app/api/providers/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import {
2020
assertPermissionsAllowed,
2121
IntegrationNotAllowedError,
22+
ModelNotAllowedError,
2223
ProviderNotAllowedError,
2324
} from '@/ee/access-control/utils/permission-check'
2425
import type { StreamingExecution } from '@/executor/types'
@@ -132,7 +133,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
132133
model,
133134
})
134135
} catch (err) {
135-
if (err instanceof ProviderNotAllowedError || err instanceof IntegrationNotAllowedError) {
136+
if (
137+
err instanceof ProviderNotAllowedError ||
138+
err instanceof ModelNotAllowedError ||
139+
err instanceof IntegrationNotAllowedError
140+
) {
136141
return NextResponse.json({ error: err.message }, { status: 403 })
137142
}
138143
throw err

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ export const ComboBox = memo(function ComboBox({
151151
const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue
152152

153153
// Permission-based filtering for model dropdowns
154-
const { isProviderAllowed, isLoading: isPermissionLoading } = usePermissionConfig()
154+
const {
155+
isProviderAllowed,
156+
isModelAllowed,
157+
isLoading: isPermissionLoading,
158+
} = usePermissionConfig()
155159

156160
// Evaluate static options if provided as a function
157161
const staticOptions = useMemo(() => {
@@ -160,17 +164,17 @@ export const ComboBox = memo(function ComboBox({
160164
if (subBlockId === 'model') {
161165
return opts.filter((opt) => {
162166
const modelId = typeof opt === 'string' ? opt : opt.id
167+
if (!isModelAllowed(modelId)) return false
163168
try {
164-
const providerId = getProviderFromModel(modelId)
165-
return isProviderAllowed(providerId)
169+
return isProviderAllowed(getProviderFromModel(modelId))
166170
} catch {
167171
return true
168172
}
169173
})
170174
}
171175

172176
return opts
173-
}, [options, subBlockId, isProviderAllowed])
177+
}, [options, subBlockId, isProviderAllowed, isModelAllowed])
174178

175179
// Normalize fetched options to match ComboBoxOption format
176180
const normalizedFetchedOptions = useMemo((): ComboBoxOption[] => {
@@ -185,9 +189,9 @@ export const ComboBox = memo(function ComboBox({
185189
if (subBlockId === 'model' && fetchOptions && normalizedFetchedOptions.length > 0) {
186190
opts = opts.filter((opt) => {
187191
const modelId = typeof opt === 'string' ? opt : opt.id
192+
if (!isModelAllowed(modelId)) return false
188193
try {
189-
const providerId = getProviderFromModel(modelId)
190-
return isProviderAllowed(providerId)
194+
return isProviderAllowed(getProviderFromModel(modelId))
191195
} catch {
192196
return true
193197
}
@@ -212,6 +216,7 @@ export const ComboBox = memo(function ComboBox({
212216
hydratedOption,
213217
subBlockId,
214218
isProviderAllowed,
219+
isModelAllowed,
215220
])
216221

217222
// Convert options to Combobox format

0 commit comments

Comments
 (0)