Skip to content

Commit faff0a8

Browse files
committed
fix(plan): preserve secret metadata during benchmark reads
1 parent 468fc3f commit faff0a8

4 files changed

Lines changed: 79 additions & 5 deletions

File tree

‎apps/sim/lib/mothership/agent-cli/read-only.test.ts‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { createEmbeddedClient } from 'sim/embed'
12
import { describe, expect, it } from 'vitest'
3+
import { runEngine } from '@/lib/mothership/agent-cli/engines'
24
import { isReadOnlyCliRequest, readOnlyCliTransport } from '@/lib/mothership/agent-cli/read-only'
35

46
describe('benchmark reference workspace inspection', () => {
@@ -49,7 +51,7 @@ describe('benchmark reference workspace inspection', () => {
4951
}
5052
)
5153

52-
it.each(['/api/v2/secrets', '/api/v2/secrets?cursor=next', '/api/v2/secrets/name'])(
54+
it.each(['/api/v2/secrets/name'])(
5355
'refuses credential values from %s before contacting the source',
5456
async (path) => {
5557
let dispatched = false
@@ -62,6 +64,67 @@ describe('benchmark reference workspace inspection', () => {
6264
}
6365
)
6466

67+
it('preserves secret metadata and pagination without returning even visible values', async () => {
68+
const metadata = {
69+
name: 'EXAMPLE_REFERENCE',
70+
scope: 'workspace',
71+
description: null,
72+
unredacted: true,
73+
role: 'admin',
74+
createdAt: '2026-01-01T00:00:00.000Z',
75+
updatedAt: '2026-01-01T00:00:00.000Z',
76+
}
77+
const transport = readOnlyCliTransport(async () =>
78+
Response.json({
79+
data: [{ ...metadata, value: 'private-fixture-value' }],
80+
nextCursor: 'next-page',
81+
})
82+
)
83+
const response = await transport('https://sim.test/api/v2/secrets?cursor=first-page')
84+
expect(response.status).toBe(200)
85+
expect(await response.json()).toEqual({ data: [metadata], nextCursor: 'next-page' })
86+
})
87+
88+
it('lets the real grep engine search paginated secret names through benchmark transport', async () => {
89+
const transport = readOnlyCliTransport(async (input) => {
90+
const last = new URL(new Request(input).url).searchParams.get('cursor') === 'next-page'
91+
return Response.json({
92+
data: [
93+
{
94+
name: last ? 'EXAMPLE_SECOND' : 'EXAMPLE_FIRST',
95+
scope: 'workspace',
96+
description: null,
97+
unredacted: true,
98+
role: 'admin',
99+
value: 'private-fixture-value',
100+
createdAt: '2026-01-01T00:00:00.000Z',
101+
updatedAt: '2026-01-01T00:00:00.000Z',
102+
},
103+
],
104+
nextCursor: last ? null : 'next-page',
105+
})
106+
})
107+
const result = await runEngine(
108+
'grep',
109+
['EXAMPLE_'],
110+
{
111+
userId: 'user-1',
112+
workspaceId: 'workspace',
113+
client: createEmbeddedClient({
114+
endpoint: 'https://sim.test',
115+
apiKey: 'fixture',
116+
workspaceId: 'workspace',
117+
transport,
118+
}),
119+
},
120+
{ scope: 'secrets' }
121+
)
122+
expect(result.exitCode).toBe(0)
123+
expect(result.stdout).toContain('EXAMPLE_FIRST')
124+
expect(result.stdout).toContain('EXAMPLE_SECOND')
125+
expect(result.stdout).not.toContain('private-fixture-value')
126+
})
127+
65128
it('retains paginated reads and table queries without allowing lookalike mutation paths', async () => {
66129
const transport = readOnlyCliTransport(async () => Response.json({ data: 'authorized result' }))
67130
for (const path of ['/api/v2/workflows?cursor=next', '/api/v2/tables/table']) {

‎apps/sim/lib/mothership/agent-cli/read-only.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { toRecord } from '@sim/utils/object'
22
import type { BlockState } from '@sim/workflow-types/workflow'
3+
import { v2ListSecretsContract, v2SecretSchema } from '@/lib/api/contracts/v2/secrets'
34
import { v2WorkflowGraphSchema } from '@/lib/api/contracts/v2/workflows'
45
import type { AgentCliRequest } from '@/lib/mothership/generated/agent-cli'
56
import { sanitizeWorkflowForSharing } from '@/lib/workflows/credentials/credential-extractor'
@@ -33,7 +34,7 @@ export function readOnlyCliTransport(transport: typeof fetch): typeof fetch {
3334
const request = new Request(input, init)
3435
const path = new URL(request.url).pathname
3536
if (
36-
/^\/api\/v2\/secrets(?:\/|$)/.test(path) ||
37+
/^\/api\/v2\/secrets\//.test(path) ||
3738
(request.method !== 'GET' &&
3839
!(request.method === 'POST' && /^\/api\/v2\/tables\/[^/]+\/query(?:\/count)?$/.test(path)))
3940
) {
@@ -45,7 +46,15 @@ export function readOnlyCliTransport(transport: typeof fetch): typeof fetch {
4546
)
4647
}
4748
const response = await transport(request)
48-
if (!response.ok || !/^\/api\/v2\/workflows\/[^/]+\/state$/.test(path)) return response
49+
if (!response.ok) return response
50+
if (path === '/api/v2/secrets') {
51+
const page = v2ListSecretsContract.response.schema.parse(await response.json())
52+
return Response.json({
53+
...page,
54+
data: page.data.map((secret) => v2SecretSchema.parse(secret)),
55+
})
56+
}
57+
if (!/^\/api\/v2\/workflows\/[^/]+\/state$/.test(path)) return response
4958
const graph = v2WorkflowGraphSchema.parse(toRecord(await response.json()).data)
5059
const sanitized = sanitizeWorkflowForSharing(
5160
{ blocks: graph.blocks as Record<string, BlockState> },

‎apps/sim/lib/mothership/memory/application/read-scope.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
createTrustedCopilotPrincipal,
1313
createTrustedOrganizationCopilotPrincipal,
1414
} from '@/lib/mothership/auth/application-delegation'
15-
import { MEMORY_SCOPE_AUDIENCE, readMemoryScope } from './read-scope'
15+
import {
16+
MEMORY_SCOPE_AUDIENCE,
17+
readMemoryScope,
18+
} from '@/lib/mothership/memory/application/read-scope'
1619

1720
const mocks = vi.hoisted(() => ({
1821
capability: vi.fn(),

‎scripts/check-unused-exports.baseline.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3562,7 +3562,6 @@
35623562
"apps/sim/lib/api/contracts/v2/secrets.ts#v2ListSecretsQuerySchema",
35633563
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretDeleteDataSchema",
35643564
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretNameSchema",
3565-
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretSchema",
35663565
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretScopeSchema",
35673566
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretSortFields",
35683567
"apps/sim/lib/api/contracts/v2/secrets.ts#v2SecretWithValueSchema",

0 commit comments

Comments
 (0)