Skip to content

Commit 82b6d40

Browse files
committed
fix(custom-model): enforce super-user boundary
1 parent 0b78f8f commit 82b6d40

20 files changed

Lines changed: 327 additions & 406 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type { SubBlockConfig } from '@/blocks/types'
1616
import { getDependsOnFields } from '@/blocks/utils'
1717
import { useGeneralSettings } from '@/hooks/queries/general-settings'
1818
import { usePermissionConfig } from '@/hooks/use-permission-config'
19-
import { isCustomModel } from '@/providers/custom-model'
19+
import { getCustomModelDisplayValue, isCustomModel } from '@/providers/custom-model'
2020
import { getProviderFromModel } from '@/providers/utils'
2121
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
2222

@@ -314,14 +314,18 @@ export const ComboBox = memo(function ComboBox({
314314
const displayValue = useMemo(() => {
315315
const raw = value?.toString() ?? ''
316316
if (!raw) return ''
317+
if (subBlockId === 'model') {
318+
const visibleModel = getCustomModelDisplayValue(raw, effectiveSuperUser)
319+
if (visibleModel !== raw) return visibleModel
320+
}
317321

318322
const match = evaluatedOptions.find((option) =>
319323
typeof option === 'string' ? option === raw : option.id === raw
320324
)
321325

322326
if (!match) return raw
323327
return typeof match === 'string' ? match : match.label
324-
}, [value, evaluatedOptions])
328+
}, [value, evaluatedOptions, subBlockId, effectiveSuperUser])
325329

326330
const [inputValue, setInputValue] = useState(displayValue)
327331
const [prevDisplayValue, setPrevDisplayValue] = useState(displayValue)

apps/sim/executor/handlers/agent/agent-handler.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -402,15 +402,15 @@ describe('AgentBlockHandler', () => {
402402
superUserModeEnabled: false,
403403
})
404404

405-
await expect(
406-
handler.execute(mockContext, mockBlock, {
407-
model: CUSTOM_MODEL_ID,
408-
customModelConfig: {
409-
provider: 'openai',
410-
model: 'gpt-future',
411-
},
412-
})
413-
).rejects.toThrow('only while Super User mode is enabled')
405+
const execution = handler.execute(mockContext, mockBlock, {
406+
model: CUSTOM_MODEL_ID,
407+
customModelConfig: {
408+
provider: 'openai',
409+
model: 'gpt-future',
410+
},
411+
})
412+
await expect(execution).rejects.toThrow('The selected model is unavailable')
413+
await expect(execution).rejects.not.toThrow(/Custom|Super User/)
414414
expect(mockExecuteProviderRequest).not.toHaveBeenCalled()
415415
})
416416

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ export class AgentBlockHandler implements BlockHandler {
123123
let autoRouting: AutoRoutingResult | null = null
124124
if (isCustomModel(configuredModel)) {
125125
if (!ctx.userId) {
126-
throw new Error('Custom models require an authenticated Super User')
126+
throw new Error('The selected model is unavailable for this execution')
127127
}
128128
const { effectiveSuperUser } = await verifyEffectiveSuperUser(ctx.userId)
129129
if (!effectiveSuperUser) {
130-
throw new Error('Custom models are available only while Super User mode is enabled')
130+
throw new Error('The selected model is unavailable for this execution')
131131
}
132132

133133
customModelConfig = parseCustomModelConfig(filteredInputs.customModelConfig)

apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('get blocks metadata', () => {
5050
expect(result.metadata).not.toHaveProperty('notion')
5151
})
5252

53-
it('omits Super User-only definitions unless the caller is effective', () => {
53+
it('always omits Super User-only definitions from Copilot metadata', () => {
5454
const block = {
5555
type: 'agent',
5656
subBlocks: [
@@ -64,9 +64,5 @@ describe('get blocks metadata', () => {
6464
} as unknown as BlockConfig
6565

6666
expect(computeBlockLevelInputs(block)).toEqual({ model: { type: 'string' } })
67-
expect(computeBlockLevelInputs(block, true)).toEqual({
68-
model: { type: 'string' },
69-
customModelConfig: { type: 'json' },
70-
})
7167
})
7268
})

apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts

Lines changed: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { isIntegrationDeploymentAvailableForVisibility } from '@/lib/integration
1010
import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils'
1111
import { isBlockTypeAccessControlExempt } from '@/lib/permission-groups/block-access'
1212
import { intersectIntegrationAllowlists } from '@/lib/permission-groups/integration-allowlist'
13-
import { verifyEffectiveSuperUser } from '@/lib/permissions/super-user'
1413
import { isCustomBlockType } from '@/blocks/custom/build-config'
1514
import { getBlock } from '@/blocks/registry'
1615
import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types'
@@ -133,18 +132,6 @@ export const getBlocksMetadataServerTool: BaseServerTool<
133132
getAllowedIntegrationsFromEnv()
134133
)
135134
const visibility = overlayVisibility()
136-
let effectiveSuperUser = false
137-
if (context?.userId) {
138-
try {
139-
effectiveSuperUser = (await verifyEffectiveSuperUser(context.userId)).effectiveSuperUser
140-
} catch (error) {
141-
// Metadata remains available, but privileged fields fail closed.
142-
logger.warn('Failed to verify effective Super User for block metadata', {
143-
userId: context.userId,
144-
error: toError(error).message,
145-
})
146-
}
147-
}
148135

149136
const result: Record<string, CopilotBlockMetadata> = {}
150137
for (const blockId of blockIds || []) {
@@ -168,8 +155,7 @@ export const getBlocksMetadataServerTool: BaseServerTool<
168155
if (specialBlock) {
169156
const { commonParameters, operationParameters } = splitParametersByOperation(
170157
specialBlock.subBlocks || [],
171-
specialBlock.inputs || {},
172-
effectiveSuperUser
158+
specialBlock.inputs || {}
173159
)
174160
metadata = {
175161
id: specialBlock.id,
@@ -210,7 +196,7 @@ export const getBlocksMetadataServerTool: BaseServerTool<
210196
// Present it as self-contained: its visible input fields + curated outputs,
211197
// no tools/operations.
212198
const visibleSubBlocks = (blockConfig.subBlocks || []).filter(
213-
(sb) => !sb.hidden && !sb.hideFromCopilot && (!sb.superUserOnly || effectiveSuperUser)
199+
(sb) => !sb.hidden && !sb.hideFromCopilot && !sb.superUserOnly
214200
)
215201
const outputs = blockConfig.outputs
216202
? Object.fromEntries(
@@ -222,9 +208,7 @@ export const getBlocksMetadataServerTool: BaseServerTool<
222208
name: blockConfig.name || blockId,
223209
description: blockConfig.longDescription || blockConfig.description || '',
224210
bestPractices: blockConfig.bestPractices,
225-
inputSchema: visibleSubBlocks.map((subBlock) =>
226-
processSubBlock(subBlock, effectiveSuperUser)
227-
),
211+
inputSchema: visibleSubBlocks.map(processSubBlock),
228212
inputDefinitions: {},
229213
tools: [],
230214
triggers: [],
@@ -267,7 +251,7 @@ export const getBlocksMetadataServerTool: BaseServerTool<
267251
if (
268252
(subBlock.mode === 'trigger' || subBlock.mode === 'trigger-advanced') &&
269253
!SYSTEM_SUBBLOCK_IDS.includes(subBlock.id) &&
270-
(!subBlock.superUserOnly || effectiveSuperUser)
254+
!subBlock.superUserOnly
271255
) {
272256
const fieldDef: any = {
273257
type: subBlock.type,
@@ -307,27 +291,22 @@ export const getBlocksMetadataServerTool: BaseServerTool<
307291
})
308292
}
309293

310-
const hiddenParamKeys = getCopilotHiddenParamKeys(blockConfig, effectiveSuperUser)
311-
const blockInputs = computeBlockLevelInputs(
312-
blockConfig,
313-
effectiveSuperUser,
314-
hiddenParamKeys
315-
)
294+
const hiddenParamKeys = getCopilotHiddenParamKeys(blockConfig)
295+
const blockInputs = computeBlockLevelInputs(blockConfig, hiddenParamKeys)
316296
const { commonParameters, operationParameters } = splitParametersByOperation(
317297
Array.isArray(blockConfig.subBlocks)
318298
? blockConfig.subBlocks.filter(
319299
(sb) =>
320300
!sb.hideFromCopilot &&
321-
(!sb.superUserOnly || effectiveSuperUser) &&
301+
!sb.superUserOnly &&
322302
sb.mode !== 'trigger' &&
323303
sb.mode !== 'trigger-advanced'
324304
)
325305
: [],
326-
blockInputs,
327-
effectiveSuperUser
306+
blockInputs
328307
)
329308

330-
const operationInputs = computeOperationLevelInputs(blockConfig, effectiveSuperUser)
309+
const operationInputs = computeOperationLevelInputs(blockConfig)
331310
const operationIds = resolveOperationIds(blockConfig, operationParameters)
332311
const operations: Record<string, any> = {}
333312
for (const opId of operationIds) {
@@ -715,7 +694,7 @@ function generateInputExample(schema: CopilotSubblockMetadata, inputDef?: any):
715694
}
716695
}
717696

718-
function processSubBlock(sb: any, effectiveSuperUser = false): CopilotSubblockMetadata {
697+
function processSubBlock(sb: any): CopilotSubblockMetadata {
719698
const processed: CopilotSubblockMetadata = {
720699
id: sb.id,
721700
type: sb.type,
@@ -785,7 +764,7 @@ function processSubBlock(sb: any, effectiveSuperUser = false): CopilotSubblockMe
785764
}
786765

787766
// Process options with icon detection
788-
const options = resolveSubblockOptions(sb, effectiveSuperUser)
767+
const options = resolveSubblockOptions(sb)
789768
if (options) {
790769
processed.options = options
791770
}
@@ -895,8 +874,7 @@ function callOptionsWithFallback(
895874
}
896875

897876
function resolveSubblockOptions(
898-
sb: any,
899-
effectiveSuperUser = false
877+
sb: any
900878
): { id: string; label?: string; hasIcon?: boolean }[] | undefined {
901879
// Skip if subblock uses fetchOptions (async network calls)
902880
if (sb.fetchOptions) {
@@ -922,7 +900,7 @@ function resolveSubblockOptions(
922900
}
923901

924902
const normalized = rawOptions
925-
.filter((opt: any) => effectiveSuperUser || !opt?.requiresSuperUser)
903+
.filter((opt: any) => !opt?.requiresSuperUser)
926904
.map((opt: any) => {
927905
if (!opt) return undefined
928906

@@ -976,8 +954,7 @@ function normalizeCondition(condition: any): any | undefined {
976954

977955
function splitParametersByOperation(
978956
subBlocks: any[],
979-
blockInputsForDescriptions?: Record<string, any>,
980-
effectiveSuperUser = false
957+
blockInputsForDescriptions?: Record<string, any>
981958
): {
982959
commonParameters: CopilotSubblockMetadata[]
983960
operationParameters: Record<string, CopilotSubblockMetadata[]>
@@ -987,7 +964,7 @@ function splitParametersByOperation(
987964

988965
for (const sb of subBlocks || []) {
989966
const cond = normalizeCondition(sb.condition)
990-
const processed = processSubBlock(sb, effectiveSuperUser)
967+
const processed = processSubBlock(sb)
991968

992969
if (cond && cond.field === 'operation' && !cond.not && cond.value !== undefined) {
993970
const values: any[] = Array.isArray(cond.value) ? cond.value : [cond.value]
@@ -1015,13 +992,10 @@ function splitParametersByOperation(
1015992
return { commonParameters, operationParameters }
1016993
}
1017994

1018-
function getCopilotHiddenParamKeys(
1019-
blockConfig: BlockConfig,
1020-
effectiveSuperUser = false
1021-
): Set<string> {
995+
function getCopilotHiddenParamKeys(blockConfig: BlockConfig): Set<string> {
1022996
const hiddenParamKeys = new Set<string>()
1023997
for (const subBlock of blockConfig.subBlocks ?? []) {
1024-
if (!subBlock.hideFromCopilot && (!subBlock.superUserOnly || effectiveSuperUser)) continue
998+
if (!subBlock.hideFromCopilot && !subBlock.superUserOnly) continue
1025999
if (subBlock.id) hiddenParamKeys.add(subBlock.id)
10261000
if (subBlock.canonicalParamId) hiddenParamKeys.add(subBlock.canonicalParamId)
10271001
}
@@ -1030,15 +1004,14 @@ function getCopilotHiddenParamKeys(
10301004

10311005
export function computeBlockLevelInputs(
10321006
blockConfig: BlockConfig,
1033-
effectiveSuperUser = false,
1034-
hiddenParamKeys = getCopilotHiddenParamKeys(blockConfig, effectiveSuperUser)
1007+
hiddenParamKeys = getCopilotHiddenParamKeys(blockConfig)
10351008
): Record<string, any> {
10361009
const inputs = blockConfig.inputs || {}
10371010
const subBlocks: any[] = Array.isArray(blockConfig.subBlocks)
10381011
? blockConfig.subBlocks.filter(
10391012
(sb) =>
10401013
!sb.hideFromCopilot &&
1041-
(!sb.superUserOnly || effectiveSuperUser) &&
1014+
!sb.superUserOnly &&
10421015
sb.mode !== 'trigger' &&
10431016
sb.mode !== 'trigger-advanced'
10441017
)
@@ -1073,15 +1046,14 @@ export function computeBlockLevelInputs(
10731046
}
10741047

10751048
function computeOperationLevelInputs(
1076-
blockConfig: BlockConfig,
1077-
effectiveSuperUser = false
1049+
blockConfig: BlockConfig
10781050
): Record<string, Record<string, any>> {
10791051
const inputs = blockConfig.inputs || {}
10801052
const subBlocks = Array.isArray(blockConfig.subBlocks)
10811053
? blockConfig.subBlocks.filter(
10821054
(sb) =>
10831055
!sb.hideFromCopilot &&
1084-
(!sb.superUserOnly || effectiveSuperUser) &&
1056+
!sb.superUserOnly &&
10851057
sb.mode !== 'trigger' &&
10861058
sb.mode !== 'trigger-advanced'
10871059
)

0 commit comments

Comments
 (0)