Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/sim/lib/execution/remote-sandbox/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ export interface SandboxSessionRequest {
cli?: { path: string; content: string; runtime?: { path: string; content: string } }
/** Extra environment variables present on every execution in the session. */
envs?: Record<string, string>
/**
* Selects ephemeral callback credentials present in the returned JSON for model redaction.
* They expire with the tool lease and do not contribute to machine or exported-file provenance.
*/
outputProvenance?: (value: unknown) => DurableSecretProvenance
/**
* This execution mounts bytes whose secret provenance is unknown, so the machine's input
* history must not stay certified clean even when the caller's own inputs are.
Expand Down
38 changes: 33 additions & 5 deletions apps/sim/lib/function-execution/execute-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ import {
MAX_BLOCK_MOUNTED_FILES,
SANDBOX_OUTPUT_DIR,
} from '@/lib/execution/remote-sandbox/sandbox-paths'
import type { SandboxCollectedFile, SandboxFile } from '@/lib/execution/remote-sandbox/types'
import type {
SandboxCollectedFile,
SandboxFile,
SandboxSessionRequest,
} from '@/lib/execution/remote-sandbox/types'
import { isExecutionResourceLimitError } from '@/lib/execution/resource-errors'
import { MAX_FUNCTION_REFERENCES } from '@/lib/function-execution/limits'
import type { SandboxExportedFile } from '@/lib/function-execution/output'
Expand Down Expand Up @@ -1030,6 +1034,7 @@ interface FunctionRouteExecutionContext {
runtimeFileSecretTraceRegistry?: ResolvedSecretTraceRegistry
runtimeInputProvenanceUnrecorded?: boolean
resolvedSecretTraceRegistry?: ResolvedSecretTraceRegistry
sessionOutputProvenance?: SandboxSessionRequest['outputProvenance']
}

/** Keeps bound file provenance in both ordinary Function results and exported artifact bytes. */
Expand Down Expand Up @@ -1276,6 +1281,12 @@ async function functionJsonResponse<T>(
context: FunctionRouteExecutionContext,
init?: ResponseInit
) {
if (context.sessionOutputProvenance && context.resolvedSecretTraceRegistry) {
Comment thread
icecrasher321 marked this conversation as resolved.
await importDurableSecretProvenance(
context.resolvedSecretTraceRegistry,
context.sessionOutputProvenance(body)
Comment thread
icecrasher321 marked this conversation as resolved.
)
}
const responseBody = {
...body,
largeValueKeys: context.largeValueKeys,
Expand Down Expand Up @@ -1543,13 +1554,14 @@ function exportUnchangedNote(sandboxPath?: string): string {
}

function exportFailure(
context: FunctionRouteExecutionContext,
error: string,
status: number,
stdout: string,
executionTime: number,
cost: FunctionExecutionCost | undefined
): NextResponse {
return NextResponse.json(
) {
return functionJsonResponse(
{
success: false,
error,
Expand All @@ -1560,6 +1572,7 @@ function exportFailure(
...(cost ? { cost } : {}),
},
},
context,
{ status }
)
}
Expand Down Expand Up @@ -1649,6 +1662,7 @@ async function maybeExportSandboxFileToWorkspace(args: {

if (!outputPath) {
return exportFailure(
routeContext,
'outputSandboxPath requires outputPath. Set outputPath to the destination workspace file, e.g. "files/result.csv".',
400,
stdout,
Expand All @@ -1662,6 +1676,7 @@ async function maybeExportSandboxFileToWorkspace(args: {

if (!resolvedWorkspaceId || routeContext.principal.kind !== 'delegated') {
return exportFailure(
routeContext,
'Workspace context required to save sandbox file to workspace',
400,
stdout,
Expand All @@ -1672,6 +1687,7 @@ async function maybeExportSandboxFileToWorkspace(args: {

if (exportedFileContent === undefined) {
return exportFailure(
routeContext,
`Sandbox file "${outputSandboxPath}" was not found or could not be read`,
500,
stdout,
Expand All @@ -1695,6 +1711,7 @@ async function maybeExportSandboxFileToWorkspace(args: {
const outputBytes = Buffer.byteLength(exportedFileContent, isBinary ? 'base64' : 'utf-8')
if (outputBytes > MAX_SANDBOX_OUTPUT_BYTES) {
return exportFailure(
routeContext,
`Sandbox output files exceed ${MAX_SANDBOX_OUTPUT_BYTES} bytes total`,
400,
stdout,
Expand Down Expand Up @@ -1779,6 +1796,7 @@ async function maybeExportSandboxFileToWorkspace(args: {
})
} catch (error) {
return exportFailure(
routeContext,
getErrorMessage(error, 'Failed to export sandbox file'),
workspaceFileExportErrorStatus(error),
stdout,
Expand All @@ -1805,6 +1823,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
if (sandboxFiles.length === 0) return null
if (sandboxFiles.length > MAX_SANDBOX_OUTPUT_FILES) {
return exportFailure(
args.routeContext,
`Too many sandbox output files requested (${sandboxFiles.length}). Maximum is ${MAX_SANDBOX_OUTPUT_FILES}.`,
400,
args.stdout,
Expand Down Expand Up @@ -1840,6 +1859,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
(args.workflowId ? (await getWorkflowById(args.workflowId))?.workspaceId : undefined)
if (!resolvedWorkspaceId || args.routeContext.principal.kind !== 'delegated') {
return exportFailure(
args.routeContext,
'Workspace context required to save sandbox files to workspace',
400,
args.stdout,
Expand All @@ -1855,6 +1875,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
const content = args.exportedFiles?.[sandboxPath]
if (content === undefined) {
return exportFailure(
args.routeContext,
`Sandbox file "${sandboxPath}" was not found or could not be read`,
500,
args.stdout,
Expand All @@ -1876,6 +1897,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
totalOutputBytes += size
if (totalOutputBytes > MAX_SANDBOX_OUTPUT_BYTES) {
return exportFailure(
args.routeContext,
`Sandbox output files exceed ${MAX_SANDBOX_OUTPUT_BYTES} bytes total`,
400,
args.stdout,
Expand Down Expand Up @@ -1928,6 +1950,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
validationPaths = validations.map((validation) => validation.vfsPath)
} catch (error) {
return exportFailure(
args.routeContext,
getErrorMessage(error, 'Invalid sandbox output destination'),
workspaceFileExportErrorStatus(error),
args.stdout,
Expand All @@ -1940,6 +1963,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
)
if (duplicateDestination) {
return exportFailure(
args.routeContext,
`Duplicate sandbox output destination: ${duplicateDestination}`,
400,
args.stdout,
Expand Down Expand Up @@ -1997,6 +2021,7 @@ async function maybeExportSandboxFilesToWorkspace(args: {
}
} catch (error) {
return exportFailure(
args.routeContext,
getErrorMessage(error, 'Failed to export sandbox files'),
workspaceFileExportErrorStatus(error),
args.stdout,
Expand Down Expand Up @@ -2145,7 +2170,8 @@ async function collectSandboxOutputFiles(args: {
// reporting success without them would read as "your script wrote nothing".
if (!resolvedWorkspaceId || !args.workflowId || !args.executionId) {
return {
response: exportFailure(
response: await exportFailure(
routeContext,
'Workspace, workflow, and execution context are required to return files from the sandbox.',
400,
args.stdout,
Expand Down Expand Up @@ -2176,7 +2202,8 @@ async function collectSandboxOutputFiles(args: {
) {
await discardUploadedExecutionFiles(files)
return {
response: exportFailure(
response: await exportFailure(
routeContext,
`Sandbox output file "${name}" contains a resolved secret value and was not returned. Write the file without embedding secret values, or export it to a workspace file where its provenance can be recorded.`,
400,
args.stdout,
Expand Down Expand Up @@ -2487,6 +2514,7 @@ export async function executeFunctionRequest(
),
mountedFileSecretProvenanceScanner,
resolvedSecretTraceRegistry: auth.resolvedSecretTraceRegistry,
sessionOutputProvenance: admittedSession?.outputProvenance,
}

const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE
Expand Down
Loading
Loading