Skip to content

Commit 40bdae7

Browse files
author
Ricardo DeMatos
committed
feat(managed-agent): require vault-authorization ack when a vault is selected
Both blocks now render a "Vault authorization" switch immediately below the Credential vaults picker: "I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them." The tool rejects execution with an actionable error when `vaults` is non-empty and the ack is not checked, so a workflow author cannot silently attach an OAuth vault to an agent they aren't authorized to run under. Why runtime, not subblock-level required: Sim's `condition` engine uses strict equality against a single value, so it cannot natively express "field is a non-empty array". Enforcing at execute time keeps the ack always visible (users see the warning up front) and fails closed on run. Also lifted the truthy-check into a shared `isTruthyAck` helper in `tools/managed_agent/normalizers.ts` — handles the real boolean, plus the `"true" | "1" | "yes"` string forms that switch values can arrive as depending on serialization. Four cases covered: - `true` accepted - `"true"` / `"True"` / `"1"` / `"yes"` accepted (case-insensitive, whitespace-trimmed) - Every other string / `false` / `undefined` / non-string non-boolean rejected Tests: 91 pass (4 new for `isTruthyAck`).
1 parent ea905bb commit 40bdae7

7 files changed

Lines changed: 105 additions & 0 deletions

File tree

apps/sim/blocks/blocks/managed_agent_cloud.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ export const ManagedAgentCloudBlock: BlockConfig = {
7878
dependsOn: ['connection'],
7979
fetchOptions: fetchManagedAgentVaultOptions,
8080
},
81+
{
82+
// Security ack — enforced at execution time when at least one vault
83+
// is selected. Rendered as a plain switch (with the same wording as
84+
// the design spec) rather than a bespoke amber banner, to stay
85+
// consistent with the rest of Sim's sub-block styling. The tool
86+
// rejects execution with an actionable error if `vaults` is
87+
// non-empty but this is false.
88+
id: 'vaultsAck',
89+
title: 'Vault authorization',
90+
type: 'switch',
91+
required: false,
92+
description:
93+
'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.',
94+
},
8195
{
8296
// Memory store — attached as a `memory_store` resource entry.
8397
id: 'memoryStoreId',
@@ -156,6 +170,11 @@ export const ManagedAgentCloudBlock: BlockConfig = {
156170
environment: { type: 'string', description: 'Cloud environment id.' },
157171
environmentType: { type: 'string', description: 'Always "cloud" for this block.' },
158172
vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' },
173+
vaultsAck: {
174+
type: 'boolean',
175+
description:
176+
'Workflow-author acknowledgement that they are authorized to use the attached vaults. Required when `vaults` is non-empty.',
177+
},
159178
memoryStoreId: { type: 'string', description: 'Optional Agent Memory Store id.' },
160179
memoryAccess: {
161180
type: 'string',

apps/sim/blocks/blocks/managed_agent_self_hosted.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = {
129129
dependsOn: ['connection'],
130130
fetchOptions: fetchManagedAgentVaultOptions,
131131
},
132+
{
133+
// Security ack — enforced at execution time when at least one vault
134+
// is selected. See the sibling comment on `managed_agent_cloud.ts`.
135+
id: 'vaultsAck',
136+
title: 'Vault authorization',
137+
type: 'switch',
138+
required: false,
139+
description:
140+
'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.',
141+
},
132142
...memorySubBlocks,
133143
{
134144
id: 'agent',
@@ -175,6 +185,11 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = {
175185
environment: { type: 'string', description: 'Self-hosted environment id.' },
176186
environmentType: { type: 'string', description: 'Always "self_hosted" for this block.' },
177187
vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' },
188+
vaultsAck: {
189+
type: 'boolean',
190+
description:
191+
'Workflow-author acknowledgement that they are authorized to use the attached vaults. Required when `vaults` is non-empty.',
192+
},
178193
memoryStoreId: {
179194
type: 'string',
180195
description:

apps/sim/tools/managed_agent/normalizers.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import { describe, expect, it } from 'vitest'
55
import {
6+
isTruthyAck,
67
normalizeEnvType,
78
normalizeFiles,
89
normalizeMemoryAccess,
@@ -204,3 +205,29 @@ describe('normalizeSessionParameters', () => {
204205
expect(normalizeSessionParameters('[broken')).toBeUndefined()
205206
})
206207
})
208+
209+
describe('isTruthyAck', () => {
210+
it('accepts the real boolean true', () => {
211+
expect(isTruthyAck(true)).toBe(true)
212+
})
213+
214+
it('accepts common string checked-forms (case-insensitive, trimmed)', () => {
215+
for (const on of ['true', 'True', 'TRUE', '1', 'yes', 'YES', ' true ']) {
216+
expect(isTruthyAck(on)).toBe(true)
217+
}
218+
})
219+
220+
it('rejects false, empty, and every other string form', () => {
221+
for (const off of [false, '', ' ', 'false', '0', 'no', 'off', 'random']) {
222+
expect(isTruthyAck(off)).toBe(false)
223+
}
224+
})
225+
226+
it('rejects undefined / null / non-string non-boolean values', () => {
227+
expect(isTruthyAck(undefined)).toBe(false)
228+
expect(isTruthyAck(null)).toBe(false)
229+
expect(isTruthyAck(1)).toBe(false)
230+
expect(isTruthyAck({})).toBe(false)
231+
expect(isTruthyAck([])).toBe(false)
232+
})
233+
})

apps/sim/tools/managed_agent/normalizers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ export function normalizeMemoryAccess(
2525
return undefined
2626
}
2727

28+
/**
29+
* A subblock's `switch` value may arrive as a real boolean or as a
30+
* string (`"true"`, `"1"`, `"yes"`) depending on how the workflow was
31+
* serialized. Treat every reasonable "checked" form as truthy; anything
32+
* else — including `false`, empty string, `undefined`, non-string
33+
* non-boolean values — as not-checked.
34+
*/
35+
export function isTruthyAck(value: unknown): boolean {
36+
if (value === true) return true
37+
if (typeof value !== 'string') return false
38+
const normalized = value.trim().toLowerCase()
39+
return normalized === 'true' || normalized === '1' || normalized === 'yes'
40+
}
41+
2842
/**
2943
* Coerces the block's file table (a JSON array of `{fileId, mountPath?}`
3044
* or the raw table-subblock shape `Array<{Key: string, Value: string}>`)

apps/sim/tools/managed_agent/run_session.server.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
sendUserMessage,
1717
} from '@/lib/managed-agents/session-client'
1818
import {
19+
isTruthyAck,
1920
normalizeEnvType,
2021
normalizeFiles,
2122
normalizeMemoryAccess,
@@ -114,6 +115,19 @@ const impl: ManagedAgentServerImpl = async (
114115
}
115116

116117
const vaultIds = normalizeStringList(params.vaults)
118+
if (vaultIds.length > 0 && !isTruthyAck(params.vaultsAck)) {
119+
// Security ack — enforced here rather than at the subblock level
120+
// because Sim's `condition` engine cannot natively test array-
121+
// non-empty. Fails closed: attaching any vault requires an explicit
122+
// confirmation that the workflow author is authorized to use it,
123+
// since the session runs with the vault's credentials.
124+
return {
125+
success: false,
126+
output: {},
127+
error:
128+
'Vault authorization is required — check the "I own or am authorized to use these vaults" acknowledgement on the block, or remove the selected vault(s).',
129+
}
130+
}
117131
const memoryStoreId = params.memoryStoreId?.trim() || undefined
118132
const memoryAccess = normalizeMemoryAccess(params.memoryAccess)
119133
const files = normalizeFiles(params.files)

apps/sim/tools/managed_agent/run_session.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ export const managedAgentRunSessionTool: ToolConfig<
8787
visibility: 'user-only',
8888
description: 'Zero or more vault ids for MCP tool auth.',
8989
},
90+
vaultsAck: {
91+
type: 'boolean',
92+
required: false,
93+
visibility: 'user-only',
94+
description:
95+
"Workflow-author acknowledgement that they are authorized to use the attached vaults. Must be true when `vaults` is non-empty; the tool rejects execution otherwise.",
96+
},
9097
memoryStoreId: {
9198
type: 'string',
9299
required: false,

apps/sim/tools/managed_agent/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ export interface ManagedAgentRunSessionParams {
2020
environmentType?: 'cloud' | 'self_hosted' | string
2121
/** Zero or more vault ids for MCP auth. Empty array allowed. */
2222
vaults?: string[]
23+
/**
24+
* Workflow-author acknowledgement that they are authorized to use the
25+
* attached vaults ("I own or am authorized to use these vaults; I
26+
* understand this means this agent can assume the identity granted by
27+
* them"). Required to be `true` when `vaults` is non-empty. Enforced by
28+
* the tool at execution time, not at the block subblock level, because
29+
* the subblock condition engine cannot natively test array-non-empty.
30+
*/
31+
vaultsAck?: boolean | string
2332
/** Optional Agent Memory Store id. */
2433
memoryStoreId?: string
2534
/**

0 commit comments

Comments
 (0)