Skip to content

Commit 0157aa9

Browse files
committed
fix(mothership): keep no raw prefix in a truncated block input
A truncated block input kept its first 200 raw characters, written before secret projection. A secret straddling that cut left a fragment that whole-literal redaction cannot match, so part of the secret reached the model. The marker now keeps nothing of the raw input: `…[input omitted; inspect with logs get <executionId> --trace]`. Inputs over the limit are echoed upstream data the caller already has or can fetch, so the preview carried little.
1 parent 11571d0 commit 0157aa9

3 files changed

Lines changed: 35 additions & 6 deletions

File tree

‎apps/sim/lib/mothership/tools/handlers/workflow/mutations.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,10 @@ describe('workflow mutation Copilot adapters', () => {
181181
}
182182
expect(code.length).toBeGreaterThan(240)
183183
expect(output.logs[0].input.code).toBe(
184-
`${code.slice(0, 200)} …[truncated; inspect with logs get execution-1 --trace]`
184+
'…[input omitted; inspect with logs get execution-1 --trace]'
185185
)
186186
expect(output.logs[0].input.note).toBe(
187-
`${'n'.repeat(200)} …[truncated; inspect with logs get execution-1 --trace]`
187+
'…[input omitted; inspect with logs get execution-1 --trace]'
188188
)
189189
expect(output.logs[0].input.language).toBe('javascript')
190190
expect(output.logs[0].output.result).toBe(code)

‎apps/sim/lib/mothership/tools/handlers/workflow/run-workflow-result-budget.test.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,33 @@ describe('run_workflow model-facing result budget', () => {
293293
expect(logs[0]?.output).toEqual({ ok: true })
294294
expect(logs[1]?.output).toEqual(expect.stringContaining(`logs get ${EXECUTION_ID} --trace`))
295295
})
296+
297+
/** A cut through a secret leaves a fragment no whole-literal redaction can match. */
298+
it('never exposes part of a secret that straddles an input truncation point', async () => {
299+
const straddling = `${'a'.repeat(190)}${SECRET}${'b'.repeat(3_000)}`
300+
mocks.executeWorkflowUseCase.mockResolvedValue({
301+
success: true,
302+
output: { done: true },
303+
logs: [
304+
{
305+
blockId: 'fn',
306+
blockName: 'Function',
307+
success: true,
308+
input: { code: straddling, note: straddling },
309+
output: { ok: true },
310+
},
311+
],
312+
metadata: { executionId: EXECUTION_ID },
313+
})
314+
315+
const settled = await executeRunWorkflow({ workflowId: 'wf-1' }, context)
316+
const projection = inspectToolResultForCopilot(settled, secretRegistry(), 'run_workflow')
317+
318+
expect(projection.safe).toBe(true)
319+
const serialized = JSON.stringify(projection.result)
320+
expect(serialized).toContain(`logs get ${EXECUTION_ID} --trace`)
321+
for (let length = 4; length <= SECRET.length; length += 1) {
322+
expect(serialized).not.toContain(SECRET.slice(0, length))
323+
}
324+
})
296325
})

‎apps/sim/lib/mothership/tools/workflow-output.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ function selectFromLogs(selectors: string[], logs: unknown[]): Record<string, un
7171
const LOG_CODE_INPUT_MAX_CHARS = 240
7272
/** Any other echoed input string over this is data the caller already has, or can fetch. */
7373
const LOG_INPUT_STRING_MAX_CHARS = 2_000
74-
const LOG_INPUT_KEEP_CHARS = 200
7574

7675
/**
7776
* Compacts the block inputs echoed back in `logs`. A Function block's `input.code` embeds the
7877
* fully serialized upstream rows, so a seven-block run repeated the same rows several times
79-
* across ~14k chars of tool result. The marker carries no length: it is written before secret
80-
* projection, so a length would disclose the length of any secret in the input.
78+
* across ~14k chars of tool result. The marker is written before secret projection, so it keeps
79+
* nothing of the raw input: a kept prefix could cut through a secret and leave a fragment no
80+
* whole-literal redaction matches, and a length would disclose the length of any secret in it.
8181
*/
8282
function compactBlockLogInputs(logs: unknown, executionId: string | undefined): unknown {
8383
if (!Array.isArray(logs)) return logs
@@ -89,7 +89,7 @@ function compactBlockLogInputs(logs: unknown, executionId: string | undefined):
8989
const limit = key === 'code' ? LOG_CODE_INPUT_MAX_CHARS : LOG_INPUT_STRING_MAX_CHARS
9090
input[key] =
9191
typeof value === 'string' && value.length > limit
92-
? `${value.slice(0, LOG_INPUT_KEEP_CHARS)} …[truncated; inspect with logs get ${reference} --trace]`
92+
? `…[input omitted; inspect with logs get ${reference} --trace]`
9393
: value
9494
}
9595
return { ...entry, input }

0 commit comments

Comments
 (0)