Skip to content

Commit 1a01720

Browse files
fix(copilot): tool selector throw falls back to access tools (fail toward strip)
Symmetric with the enabled-throw fix: when tools.config.tool throws on partial params, scan all access tools instead of returning, so a hosted key can't slip through. Test added.
1 parent 2eb8416 commit 1a01720

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/validation.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,22 @@ const throwGateBlockConfig = {
123123
tools: { access: ['throw_gate_tool'], config: { tool: () => 'throw_gate_tool' } },
124124
}
125125

126+
// Block whose tool selector throws — should fall back to scanning access tools (video_falai).
127+
const throwSelectorBlockConfig = {
128+
type: 'throw_selector_block',
129+
name: 'Throw Selector Block',
130+
outputs: {},
131+
subBlocks: [{ id: 'provider', type: 'dropdown' }],
132+
tools: {
133+
access: ['video_falai'],
134+
config: {
135+
tool: () => {
136+
throw new Error('selector boom')
137+
},
138+
},
139+
},
140+
}
141+
126142
// Tool registry stand-in for the hosted-tool tests.
127143
const toolsByIdMock: Record<string, unknown> = {
128144
video_falai: { id: 'video_falai', hosting: { apiKeyParam: 'apiKey' } },
@@ -170,7 +186,9 @@ vi.mock('@/blocks/registry', () => ({
170186
? imageBlockConfig
171187
: type === 'throw_gate_block'
172188
? throwGateBlockConfig
173-
: undefined,
189+
: type === 'throw_selector_block'
190+
? throwSelectorBlockConfig
191+
: undefined,
174192
}))
175193

176194
vi.mock('@/blocks/utils', () => ({
@@ -714,6 +732,24 @@ describe('preValidateCredentialInputs (hosted-tool blocks)', () => {
714732
expect(result.errors[0]).toMatchObject({ blockId: 'video-1', field: 'apiKey' })
715733
})
716734

735+
it('strips apiKey when the tool selector throws (falls back to access tools)', async () => {
736+
const operations = [
737+
{
738+
operation_type: 'add' as const,
739+
block_id: 'sel-1',
740+
params: {
741+
type: 'throw_selector_block',
742+
inputs: { provider: 'falai', apiKey: 'user-key' },
743+
},
744+
},
745+
]
746+
747+
const result = await preValidateCredentialInputs(operations, ctx)
748+
749+
expect(result.filteredOperations[0]?.params?.inputs?.apiKey).toBeUndefined()
750+
expect(result.errors).toHaveLength(1)
751+
})
752+
717753
it('strips apiKey when a tool hosting enabled predicate throws (fail toward stripping)', async () => {
718754
const operations = [
719755
{

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/validation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,17 +1332,19 @@ export async function preValidateCredentialInputs(
13321332
if (!isHosted || !blockConfig?.tools) return
13331333

13341334
// Resolve which tool(s) the current inputs select. With a selector there is exactly one active
1335-
// tool; without one, every accessible tool is a candidate.
1335+
// tool; without one (or if the selector throws on partial params), every accessible tool is a
1336+
// candidate — failing toward considering all hosted params so a key can't slip through.
1337+
const accessToolIds = blockConfig.tools.access ?? []
13361338
let candidateToolIds: string[]
13371339
const toolSelector = blockConfig.tools.config?.tool
13381340
if (toolSelector) {
13391341
try {
13401342
candidateToolIds = [toolSelector(toolParams)]
13411343
} catch {
1342-
return
1344+
candidateToolIds = accessToolIds
13431345
}
13441346
} else {
1345-
candidateToolIds = blockConfig.tools.access ?? []
1347+
candidateToolIds = accessToolIds
13461348
}
13471349

13481350
const managedFieldIds = new Set<string>()

0 commit comments

Comments
 (0)