2929 RcloneMountConfig ,
3030 S3FilesMountConfig ,
3131)
32- from agents .sandbox .errors import MountConfigError
32+ from agents .sandbox .errors import MountCommandError , MountConfigError
3333from agents .sandbox .session .base_sandbox_session import BaseSandboxSession
34+ from agents .sandbox .session .events import SandboxSessionEvent
35+ from agents .sandbox .session .manager import Instrumentation
36+ from agents .sandbox .session .sandbox_session import SandboxSession
37+ from agents .sandbox .session .sinks import CallbackSink
3438from agents .sandbox .snapshot import NoopSnapshot
3539from agents .sandbox .types import ExecResult
3640from tests .utils .factories import TestSessionState
@@ -76,13 +80,16 @@ async def hydrate_workspace(self, data: io.IOBase) -> None:
7680
7781
7882class _MountpointApplySession (BaseSandboxSession ):
79- def __init__ (self ) -> None :
83+ def __init__ (self , * , mount_exit_code : int = 0 , mount_stderr : bytes = b"" ) -> None :
8084 self .state = TestSessionState (
8185 session_id = uuid .uuid4 (),
8286 manifest = Manifest (root = "/workspace" ),
8387 snapshot = NoopSnapshot (id = str (uuid .uuid4 ())),
8488 )
89+ self ._mount_exit_code = mount_exit_code
90+ self ._mount_stderr = mount_stderr
8591 self .exec_calls : list [list [str ]] = []
92+ self .write_calls : list [tuple [Path , bytes ]] = []
8693
8794 async def read (self , path : Path , * , user : object = None ) -> io .BytesIO :
8895 _ = (path , user )
@@ -92,12 +99,15 @@ async def shutdown(self) -> None:
9299 return None
93100
94101 async def write (self , path : Path , data : io .IOBase , * , user : object = None ) -> None :
95- _ = ( path , data , user )
96- raise AssertionError ( "write() should not be called in these tests" )
102+ _ = user
103+ self . write_calls . append (( path , data . read ()) )
97104
98105 async def running (self ) -> bool :
99106 return True
100107
108+ def persist_workspace_skip_paths (self ) -> set [Path ]:
109+ return self ._persist_workspace_skip_relpaths ()
110+
101111 async def _exec_internal (
102112 self ,
103113 * command : str | Path ,
@@ -106,6 +116,15 @@ async def _exec_internal(
106116 _ = timeout
107117 command_strs = [str (part ) for part in command ]
108118 self .exec_calls .append (command_strs )
119+ if (
120+ len (command_strs ) >= 3
121+ and command_strs [:2 ] == ["sh" , "-lc" ]
122+ and "mount-s3 " in command_strs [2 ]
123+ and "command -v " not in command_strs [2 ]
124+ ):
125+ return ExecResult (
126+ exit_code = self ._mount_exit_code , stdout = b"" , stderr = self ._mount_stderr
127+ )
109128 return ExecResult (exit_code = 0 , stdout = b"" , stderr = b"" )
110129
111130 async def persist_workspace (self ) -> io .IOBase :
@@ -380,15 +399,23 @@ async def test_gcs_mount_uses_runtime_endpoint_override_without_mutating_pattern
380399 ["sh" , "-lc" , "command -v mount-s3 >/dev/null 2>&1" ],
381400 ["mkdir" , "-p" , "/workspace/remote" ],
382401 ]
383- assert len (session .exec_calls ) == 3
384-
385- mount_command = session .exec_calls [2 ]
402+ assert len (session .exec_calls ) == 5
403+ assert len (session .write_calls ) == 1
404+ env_path , env_payload = session .write_calls [0 ]
405+ assert env_path .as_posix ().startswith (".sandbox-mountpoint-env/" )
406+ assert env_path .name .endswith (".env" )
407+ assert env_payload == b"export AWS_ACCESS_KEY_ID=access\n export AWS_SECRET_ACCESS_KEY=secret\n "
408+
409+ mount_command = session .exec_calls [- 1 ]
386410 assert mount_command [:2 ] == ["sh" , "-lc" ]
387411 assert "mount-s3" in mount_command [2 ]
412+ assert "AWS_ACCESS_KEY_ID=access" not in mount_command [2 ]
413+ assert "AWS_SECRET_ACCESS_KEY=secret" not in mount_command [2 ]
414+ assert ".sandbox-mountpoint-env" in mount_command [2 ]
388415 assert "--region us-east1" in mount_command [2 ]
389416 assert "--endpoint-url https://storage.googleapis.com" in mount_command [2 ]
390417 assert "--upload-checksums off" in mount_command [2 ]
391- assert mount_command [ 2 ]. endswith ( "bucket /workspace/remote" )
418+ assert "bucket /workspace/remote" in mount_command [ 2 ]
392419
393420
394421@pytest .mark .asyncio
@@ -416,19 +443,29 @@ async def test_s3_mountpoint_writable_mode_enables_overwrite_and_delete() -> Non
416443 ["sh" , "-lc" , "command -v mount-s3 >/dev/null 2>&1" ],
417444 ["mkdir" , "-p" , "/workspace/remote" ],
418445 ]
419- assert len (session .exec_calls ) == 3
420-
421- mount_command = session .exec_calls [2 ]
446+ assert len (session .exec_calls ) == 5
447+ assert len (session .write_calls ) == 1
448+ env_path , env_payload = session .write_calls [0 ]
449+ assert env_path .as_posix ().startswith (".sandbox-mountpoint-env/" )
450+ assert env_path .name .endswith (".env" )
451+ assert env_payload == (
452+ b"export AWS_ACCESS_KEY_ID=access\n "
453+ b"export AWS_SECRET_ACCESS_KEY=secret\n "
454+ b"export AWS_SESSION_TOKEN=token\n "
455+ )
456+
457+ mount_command = session .exec_calls [- 1 ]
422458 assert mount_command [:2 ] == ["sh" , "-lc" ]
423459 assert "mount-s3" in mount_command [2 ]
424460 assert "--read-only" not in mount_command [2 ]
425461 assert "--allow-overwrite" in mount_command [2 ]
426462 assert "--allow-delete" in mount_command [2 ]
427463 assert "--region us-east-1" in mount_command [2 ]
428- assert "AWS_ACCESS_KEY_ID=access" in mount_command [2 ]
429- assert "AWS_SECRET_ACCESS_KEY=secret" in mount_command [2 ]
430- assert "AWS_SESSION_TOKEN=token" in mount_command [2 ]
431- assert mount_command [2 ].endswith ("bucket /workspace/remote" )
464+ assert "AWS_ACCESS_KEY_ID=access" not in mount_command [2 ]
465+ assert "AWS_SECRET_ACCESS_KEY=secret" not in mount_command [2 ]
466+ assert "AWS_SESSION_TOKEN=token" not in mount_command [2 ]
467+ assert ".sandbox-mountpoint-env" in mount_command [2 ]
468+ assert "bucket /workspace/remote" in mount_command [2 ]
432469
433470
434471@pytest .mark .asyncio
@@ -456,9 +493,14 @@ async def test_gcs_mountpoint_writable_mode_enables_overwrite_and_delete() -> No
456493 ["sh" , "-lc" , "command -v mount-s3 >/dev/null 2>&1" ],
457494 ["mkdir" , "-p" , "/workspace/remote" ],
458495 ]
459- assert len (session .exec_calls ) == 3
460-
461- mount_command = session .exec_calls [2 ]
496+ assert len (session .exec_calls ) == 5
497+ assert len (session .write_calls ) == 1
498+ env_path , env_payload = session .write_calls [0 ]
499+ assert env_path .as_posix ().startswith (".sandbox-mountpoint-env/" )
500+ assert env_path .name .endswith (".env" )
501+ assert env_payload == b"export AWS_ACCESS_KEY_ID=access\n export AWS_SECRET_ACCESS_KEY=secret\n "
502+
503+ mount_command = session .exec_calls [- 1 ]
462504 assert mount_command [:2 ] == ["sh" , "-lc" ]
463505 assert "mount-s3" in mount_command [2 ]
464506 assert "--read-only" not in mount_command [2 ]
@@ -467,9 +509,58 @@ async def test_gcs_mountpoint_writable_mode_enables_overwrite_and_delete() -> No
467509 assert "--region us-east1" in mount_command [2 ]
468510 assert "--endpoint-url https://storage.googleapis.com" in mount_command [2 ]
469511 assert "--upload-checksums off" in mount_command [2 ]
470- assert "AWS_ACCESS_KEY_ID=access" in mount_command [2 ]
471- assert "AWS_SECRET_ACCESS_KEY=secret" in mount_command [2 ]
472- assert mount_command [2 ].endswith ("bucket /workspace/remote" )
512+ assert "AWS_ACCESS_KEY_ID=access" not in mount_command [2 ]
513+ assert "AWS_SECRET_ACCESS_KEY=secret" not in mount_command [2 ]
514+ assert ".sandbox-mountpoint-env" in mount_command [2 ]
515+ assert "bucket /workspace/remote" in mount_command [2 ]
516+
517+
518+ @pytest .mark .asyncio
519+ async def test_s3_mountpoint_failure_redacts_credentials_from_errors_and_events () -> None :
520+ events : list [SandboxSessionEvent ] = []
521+ inner = _MountpointApplySession (
522+ mount_exit_code = 1 ,
523+ mount_stderr = b"bad credentials: access secret token" ,
524+ )
525+ session = SandboxSession (
526+ inner ,
527+ instrumentation = Instrumentation (
528+ sinks = [CallbackSink (lambda event , _session : events .append (event ))]
529+ ),
530+ )
531+ pattern = MountpointMountPattern ()
532+
533+ with pytest .raises (MountCommandError ) as exc_info :
534+ await pattern .apply (
535+ session ,
536+ Path ("/workspace/remote" ),
537+ MountpointMountConfig (
538+ bucket = "bucket" ,
539+ access_key_id = "access" ,
540+ secret_access_key = "secret" ,
541+ session_token = "token" ,
542+ prefix = None ,
543+ region = "us-east-1" ,
544+ endpoint_url = None ,
545+ mount_type = "s3_mount" ,
546+ read_only = False ,
547+ ),
548+ )
549+
550+ context = exc_info .value .context
551+ command = str (context ["command" ])
552+ stderr = str (context ["stderr" ])
553+ assert "REDACTED" in stderr
554+ assert ".sandbox-mountpoint-env" in command
555+ assert any (
556+ path .as_posix ().startswith (".sandbox-mountpoint-env/" )
557+ for path in inner .persist_workspace_skip_paths ()
558+ )
559+ serialized_events = "\n " .join (event .model_dump_json () for event in events )
560+ for sensitive_value in ("access" , "secret" , "token" ):
561+ assert sensitive_value not in command
562+ assert sensitive_value not in stderr
563+ assert sensitive_value not in serialized_events
473564
474565
475566@pytest .mark .asyncio
0 commit comments