@@ -7,7 +7,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
77import { captureInvocation , type InvocationInput , type Phase } from '../agents/contract.ts' ;
88import { AGENT_IMAGE , assertBuiltAgentImage , buildAgentImage } from '../agents/container/image.ts' ;
99import { createContainerProfile , disposeContainerProfile } from '../agents/container/profile.ts' ;
10- import { createValidatedContainer , prepareTaskFilesystems , removeTaskFilesystems , runContainer ,
10+ import { createValidatedContainer , prepareTaskFilesystems , removeTaskFilesystems , runContainer , startValidatedContainer ,
1111 hasExactOptions , validateContainer } from '../agents/container/run.ts' ;
1212import { createTaskClone } from '../git/clone.ts' ;
1313import { createVendorNetwork , removeVendorNetwork , type VendorNetwork } from '../agents/network/network.ts' ;
@@ -102,6 +102,13 @@ describe('real Docker agent isolation', () => {
102102 expect ( runContainer ( profile ( data , phase , 'phase-worktree' ) ) ) . toBe ( '' ) ;
103103 } , 60_000 ) ;
104104
105+ it ( 'removes the invocation proxy and network after the container settles' , ( ) => {
106+ const data = fixture ( ) , valid = profile ( data , 'planning' , 'noop' ) ;
107+ expect ( runContainer ( valid ) ) . toBe ( '' ) ;
108+ expect ( spawnSync ( 'docker' , [ 'container' , 'inspect' , valid . network . proxyContainer ] ) . status ) . not . toBe ( 0 ) ;
109+ expect ( spawnSync ( 'docker' , [ 'network' , 'inspect' , valid . network . name ] ) . status ) . not . toBe ( 0 ) ;
110+ } , 60_000 ) ;
111+
105112 it ( 'runs read-only with no root capabilities, host paths, inherited secrets, or writable tools' , ( ) => {
106113 const data = fixture ( ) ;
107114 process . env . HOST_SECRET_SENTINEL = 'must-not-reach-container' ;
@@ -257,10 +264,10 @@ describe('real Docker agent isolation', () => {
257264
258265 it ( 'does not remove an active container when a duplicate attempt name collides' , ( ) => {
259266 const data = fixture ( ) , captured = invocation ( data . clone , 'planning' ) ;
260- const common = governed ( captured ) ;
261- const first = createContainerProfile ( { ...common , filesystems : data . filesystems ,
267+ const duplicateInvocation = captureInvocation ( { ... captured } ) ;
268+ const first = createContainerProfile ( { ...governed ( captured ) , filesystems : data . filesystems ,
262269 inputDirectory : data . input , codexAuthFile : data . fakeAuth , imageId } ) ;
263- const duplicate = createContainerProfile ( { ...common , filesystems : data . filesystems ,
270+ const duplicate = createContainerProfile ( { ...governed ( duplicateInvocation ) , filesystems : data . filesystems ,
264271 inputDirectory : data . input , codexAuthFile : data . fakeAuth , imageId } ) ;
265272 profiles . push ( first , duplicate ) ;
266273 docker ( ...first . args ) ; containers . add ( first . name ) ;
@@ -301,6 +308,14 @@ describe('real Docker agent isolation', () => {
301308 expect ( ( ) => validateContainer ( valid . name , valid ) ) . toThrow ( 'DNS configuration' ) ;
302309 docker ( 'rm' , '--force' , valid . name ) ; containers . delete ( valid . name ) ;
303310
311+ for ( const extra of [ [ '--add-host=api.openai.com:127.0.0.1' ] , [ '--publish=127.0.0.1::3128' ] ] ) {
312+ const changedArgs = [ ...valid . args . slice ( 0 , valid . args . indexOf ( imageId ) ) , ...extra ,
313+ ...valid . args . slice ( valid . args . indexOf ( imageId ) ) ] ;
314+ docker ( ...changedArgs ) ; containers . add ( valid . name ) ;
315+ expect ( ( ) => validateContainer ( valid . name , valid ) ) . toThrow ( 'host or port configuration' ) ;
316+ docker ( 'rm' , '--force' , valid . name ) ; containers . delete ( valid . name ) ;
317+ }
318+
304319 const imageIndex = valid . args . indexOf ( imageId ) ;
305320 const namespaceArgs = [ ...valid . args . slice ( 0 , imageIndex ) , '--uts=host' , ...valid . args . slice ( imageIndex ) ] ;
306321 docker ( ...namespaceArgs ) ; containers . add ( valid . name ) ;
@@ -319,10 +334,23 @@ describe('real Docker agent isolation', () => {
319334 try {
320335 docker ( 'run' , '--detach' , '--name' , rogue , `--network=${ valid . network . name } ` , '--entrypoint' , 'node' , imageId ,
321336 '-e' , 'setInterval(()=>{},1000)' ) ;
322- expect ( ( ) => createValidatedContainer ( valid ) ) . toThrow ( 'network or proxy changed ' ) ;
337+ expect ( ( ) => createValidatedContainer ( valid ) ) . toThrow ( 'cleanup did not settle ' ) ;
323338 const absent = spawnSync ( 'docker' , [ 'container' , 'inspect' , valid . name ] , { encoding : 'utf8' } ) ;
324339 expect ( absent . status ) . not . toBe ( 0 ) ;
325- } finally { spawnSync ( 'docker' , [ 'rm' , '--force' , rogue ] , { stdio : 'ignore' } ) ; }
340+ } finally {
341+ spawnSync ( 'docker' , [ 'rm' , '--force' , rogue ] , { stdio : 'ignore' } ) ;
342+ disposeContainerProfile ( valid ) ;
343+ }
344+ } , 60_000 ) ;
345+
346+ it ( 'revalidates the agent attachment immediately before start' , ( ) => {
347+ const data = fixture ( ) , valid = profile ( data , 'planning' , 'must-not-run' ) ;
348+ createValidatedContainer ( valid ) ; containers . add ( valid . name ) ;
349+ docker ( 'network' , 'disconnect' , valid . network . name , valid . name ) ;
350+ docker ( 'network' , 'connect' , 'bridge' , valid . name ) ;
351+ expect ( ( ) => startValidatedContainer ( valid ) ) . toThrow ( / n e t w o r k a t t a c h m e n t | l o c k d o w n / ) ;
352+ containers . delete ( valid . name ) ;
353+ expect ( spawnSync ( 'docker' , [ 'container' , 'inspect' , valid . name ] ) . status ) . not . toBe ( 0 ) ;
326354 } , 60_000 ) ;
327355
328356 it ( 'creates containers from the captured immutable image rather than its mutable tag' , ( ) => {
0 commit comments