mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-16 14:50:06 +08:00
fix(execution): count the runtime payload as secret material in scope
An execution with no mounted files and no env secret still carries `params` and `contextVariables` into the sandbox — the runtime payload is serialized into a private-input file, so resolved block outputs and workflow variables land as plaintext regardless of `_sandboxFiles`. The scope predicate only looked at mounts and env secrets, so a binary derived from them was classified exact-empty. The route has no catalog for those values and cannot tell a secret-bearing one from an ordinary one, so they count as in scope. Only an execution with nothing at all in scope earns an exact-empty binary. This narrows where the relaxation applies rather than regressing anything: every binary export was unknown before this branch, so a workflow Function block carrying block references keeps exactly the behavior it has today. The mothership path is unaffected — its tool sets no contextVariables, blockData, or workflowVariables, which is the case this branch exists to fix. Values, not keys, for the params check: `executionParams._context` is set to undefined before the context is built, so a key count reads every execution as carrying params. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ddc1c1ae73
commit
754e37cedb
@@ -890,6 +890,40 @@ describe('Function Execute API Route', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps a binary export unknown when resolved context variables reach the sandbox', async () => {
|
||||
envFlagsMock.isRemoteSandboxEnabled = true
|
||||
mockExecuteInSandbox.mockResolvedValueOnce({
|
||||
result: 'done',
|
||||
stdout: '',
|
||||
sandboxId: 'sandbox-123',
|
||||
exportedFiles: { '/home/user/small.jpg': '/9j/4AAQ' },
|
||||
})
|
||||
|
||||
const response = await POST(
|
||||
createMockRequest('POST', {
|
||||
code: 'print("done")',
|
||||
language: 'python',
|
||||
workspaceId: 'workspace-1',
|
||||
// Resolved upstream block output — the route has no catalog to classify it.
|
||||
contextVariables: { upstreamValue: 'could-be-anything' },
|
||||
outputs: {
|
||||
files: [
|
||||
{
|
||||
path: 'files/small.jpg',
|
||||
sandboxPath: '/home/user/small.jpg',
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(mockWriteWorkspaceFileByPath).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ secretProvenance: { status: 'unknown' } })
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps a binary export unknown when files were mounted without a provenance envelope', async () => {
|
||||
envFlagsMock.isRemoteSandboxEnabled = true
|
||||
mockExecuteInSandbox.mockResolvedValueOnce({
|
||||
|
||||
@@ -990,6 +990,12 @@ interface FunctionRouteExecutionContext {
|
||||
outputSecretPlaintextsByName: Map<string, string>
|
||||
mountedFileSecretProvenanceScanner?: MountedFileSecretProvenanceScanner
|
||||
hasMountedSandboxFiles: boolean
|
||||
/**
|
||||
* Whether the serialized runtime payload carried `params` or `contextVariables` into the sandbox.
|
||||
* Those are resolved block outputs and workflow variables — the route has no catalog for them, so
|
||||
* it cannot tell a secret-bearing one from an ordinary value.
|
||||
*/
|
||||
hasUnclassifiedRuntimeInputs: boolean
|
||||
}
|
||||
|
||||
type ResolvedSecretNamesMetadataType =
|
||||
@@ -1219,9 +1225,15 @@ function activateOutputSecretProvenance(
|
||||
* are unclassifiable rather than clean: absence of an envelope is absence of evidence, not evidence
|
||||
* the mount carried nothing. Those fail closed here so the classification can never be stronger
|
||||
* than what the caller actually attested to.
|
||||
*
|
||||
* The serialized runtime payload counts too. It carries `params` and `contextVariables` — resolved
|
||||
* block outputs and workflow variables — into the sandbox as a private-input file, and the route
|
||||
* has no catalog to tell a secret-bearing one from an ordinary value. Only an execution with
|
||||
* nothing at all in scope earns an exact-empty binary.
|
||||
*/
|
||||
function hasSecretMaterialInScope(context: FunctionRouteExecutionContext): boolean {
|
||||
if (context.outputSecretPlaintextsByName.size > 0) return true
|
||||
if (context.hasUnclassifiedRuntimeInputs) return true
|
||||
const scanner = context.mountedFileSecretProvenanceScanner
|
||||
return scanner ? scanner.hasSecrets : context.hasMountedSandboxFiles
|
||||
}
|
||||
@@ -2031,6 +2043,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
|
||||
outputSecretPlaintextsByName: new Map(),
|
||||
mountedFileSecretProvenanceScanner,
|
||||
hasMountedSandboxFiles: (_sandboxFiles?.length ?? 0) > 0,
|
||||
// Values, not keys: `executionParams._context` is explicitly set to undefined just above,
|
||||
// so a key count would read every execution as carrying params. Raised below once the
|
||||
// resolved context variables are known.
|
||||
hasUnclassifiedRuntimeInputs: Object.values(executionParams).some(
|
||||
(value) => value !== undefined
|
||||
),
|
||||
}
|
||||
for (const [name, plaintext] of Object.entries(envVars)) {
|
||||
if (!plaintext) continue
|
||||
@@ -2074,6 +2092,11 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
|
||||
...codeResolution.contextVariables,
|
||||
...preResolvedContextVariables,
|
||||
}
|
||||
// Resolved block outputs and workflow variables ride into the sandbox inside the runtime
|
||||
// payload, so an output file can carry them even with nothing mounted and no env secret.
|
||||
if (Object.keys(contextVariables).length > 0) {
|
||||
routeContext.hasUnclassifiedRuntimeInputs = true
|
||||
}
|
||||
const compilation = await compileCodePlaceholders({
|
||||
code: codeResolution.resolvedCode,
|
||||
language: lang,
|
||||
|
||||
Reference in New Issue
Block a user