@@ -15,6 +15,11 @@ import {
1515} from '@sim/testing'
1616import { NextRequest } from 'next/server'
1717import { afterAll , beforeEach , describe , expect , it , vi } from 'vitest'
18+ import {
19+ BILLING_ATTRIBUTION_HEADER ,
20+ type BillingAttributionSnapshot ,
21+ serializeBillingAttributionHeader ,
22+ } from '@/lib/billing/core/billing-attribution'
1823import { INTERNAL_EXECUTION_DEADLINE_HEADER } from '@/lib/execution/execution-deadline-header'
1924import {
2025 MOUNTED_WORKSPACE_FILES_PROVENANCE_KEY ,
@@ -39,6 +44,22 @@ function grantedAccess(workspaceId: string) {
3944 }
4045}
4146
47+ function billingAttributionHeaders ( workspaceId = 'workspace-1' ) : Record < string , string > {
48+ const attribution : BillingAttributionSnapshot = {
49+ actorUserId : 'user-123' ,
50+ workspaceId,
51+ organizationId : null ,
52+ billedAccountUserId : 'user-123' ,
53+ billingEntity : { type : 'user' , id : 'user-123' } ,
54+ billingPeriod : {
55+ start : '2026-08-01T00:00:00.000Z' ,
56+ end : '2026-09-01T00:00:00.000Z' ,
57+ } ,
58+ payerSubscription : null ,
59+ }
60+ return { [ BILLING_ATTRIBUTION_HEADER ] : serializeBillingAttributionHeader ( attribution ) }
61+ }
62+
4263const {
4364 mockExecuteInSandbox,
4465 mockExecuteInIsolatedVM,
@@ -318,26 +339,8 @@ describe('Function Execute API Route', () => {
318339 expect ( mockWriteWorkspaceFileByPath ) . not . toHaveBeenCalled ( )
319340 } )
320341
321- it ( 'rejects an export whose workspace is derived from a body-supplied workflowId ' , async ( ) => {
342+ it ( 'fails closed before deriving an export workspace from incomplete workflow context ' , async ( ) => {
322343 envFlagsMock . isRemoteSandboxEnabled = true
323- mockExecuteInSandbox . mockResolvedValueOnce ( {
324- result : 'done' ,
325- stdout : 'ok' ,
326- sandboxId : 'sandbox-123' ,
327- exportedFiles : { '/tmp/out.txt' : 'owned by attacker' } ,
328- } )
329- workflowsUtilsMock . getWorkflowById . mockResolvedValueOnce ( {
330- id : 'workflow-victim' ,
331- workspaceId : 'workspace-victim' ,
332- } )
333- mockResolveWorkspaceAccess . mockResolvedValue ( {
334- exists : true ,
335- hasAccess : false ,
336- canWrite : false ,
337- canAdmin : false ,
338- workspace : { id : 'workspace-victim' } ,
339- permission : null ,
340- } )
341344
342345 const req = createMockRequest ( 'POST' , {
343346 code : 'print("done")' ,
@@ -350,7 +353,12 @@ describe('Function Execute API Route', () => {
350353
351354 const response = await POST ( req )
352355
353- expect ( response . status ) . toBe ( 403 )
356+ expect ( response . status ) . toBe ( 503 )
357+ await expect ( response . json ( ) ) . resolves . toMatchObject ( {
358+ retryable : false ,
359+ code : 'sandbox_usage_attribution_invalid' ,
360+ } )
361+ expect ( mockExecuteInSandbox ) . not . toHaveBeenCalled ( )
354362 expect ( mockWriteWorkspaceFileByPath ) . not . toHaveBeenCalled ( )
355363 } )
356364
@@ -537,6 +545,85 @@ describe('Function Execute API Route', () => {
537545 expect ( mockExecuteInIsolatedVM ) . not . toHaveBeenCalled ( )
538546 } )
539547
548+ it . each ( [
549+ {
550+ label : 'execution ID' ,
551+ body : { workflowId : 'workflow-1' , workspaceId : 'workspace-1' } ,
552+ } ,
553+ {
554+ label : 'trusted billing attribution' ,
555+ body : {
556+ workflowId : 'workflow-1' ,
557+ workspaceId : 'workspace-1' ,
558+ executionId : 'execution-1' ,
559+ } ,
560+ } ,
561+ ] ) (
562+ 'fails before remote sandbox creation when workflow usage lacks $label' ,
563+ async ( { body } ) => {
564+ envFlagsMock . isRemoteSandboxEnabled = true
565+
566+ const response = await POST (
567+ createMockRequest ( 'POST' , { code : 'print("ready")' , language : 'python' , ...body } )
568+ )
569+
570+ expect ( response . status ) . toBe ( 503 )
571+ await expect ( response . json ( ) ) . resolves . toMatchObject ( {
572+ success : false ,
573+ retryable : false ,
574+ code : 'sandbox_usage_attribution_invalid' ,
575+ } )
576+ expect ( mockExecuteInSandbox ) . not . toHaveBeenCalled ( )
577+ }
578+ )
579+
580+ it ( 'passes complete trusted workflow attribution to remote sandbox billing' , async ( ) => {
581+ envFlagsMock . isRemoteSandboxEnabled = true
582+
583+ const response = await POST (
584+ createMockRequest (
585+ 'POST' ,
586+ {
587+ code : 'print("ready")' ,
588+ language : 'python' ,
589+ workflowId : 'workflow-1' ,
590+ workspaceId : 'workspace-1' ,
591+ executionId : 'execution-1' ,
592+ } ,
593+ billingAttributionHeaders ( )
594+ )
595+ )
596+
597+ expect ( response . status ) . toBe ( 200 )
598+ expect ( mockExecuteInSandbox ) . toHaveBeenCalledWith (
599+ expect . objectContaining ( {
600+ usageContext : expect . objectContaining ( {
601+ workspaceId : 'workspace-1' ,
602+ workflowId : 'workflow-1' ,
603+ executionId : 'execution-1' ,
604+ billingAttribution : expect . objectContaining ( { actorUserId : 'user-123' } ) ,
605+ } ) ,
606+ } )
607+ )
608+ } )
609+
610+ it ( 'allows a non-workflow remote call without sandbox usage attribution' , async ( ) => {
611+ envFlagsMock . isRemoteSandboxEnabled = true
612+
613+ const response = await POST (
614+ createMockRequest ( 'POST' , {
615+ code : 'print("ready")' ,
616+ language : 'python' ,
617+ workspaceId : 'workspace-1' ,
618+ } )
619+ )
620+
621+ expect ( response . status ) . toBe ( 200 )
622+ expect ( mockExecuteInSandbox ) . toHaveBeenCalledWith (
623+ expect . objectContaining ( { usageContext : undefined } )
624+ )
625+ } )
626+
540627 it ( 'forces import-free JavaScript into the remote runtime when a Sim sandbox is selected' , async ( ) => {
541628 envFlagsMock . isRemoteSandboxEnabled = true
542629
0 commit comments