From 1bc0593a35dd8c5b2cbf09bd51c8b8991da724c9 Mon Sep 17 00:00:00 2001 From: matthewflint <277024436+matthewflint@users.noreply.github.com> Date: Fri, 15 May 2026 14:40:40 +0300 Subject: [PATCH] fix: redact mountpoint credentials in sandbox mount errors Keep the executable mount-s3 command unchanged, but redact AWS credential environment variables when reporting MountCommandError so failures do not expose secrets. --- src/agents/sandbox/entries/mounts/patterns.py | 31 ++++++++++++++----- tests/sandbox/test_mount_patterns.py | 25 +++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 tests/sandbox/test_mount_patterns.py diff --git a/src/agents/sandbox/entries/mounts/patterns.py b/src/agents/sandbox/entries/mounts/patterns.py index 931fa03450..b780d17e35 100644 --- a/src/agents/sandbox/entries/mounts/patterns.py +++ b/src/agents/sandbox/entries/mounts/patterns.py @@ -108,6 +108,22 @@ async def _write_sensitive_config_file( ) +def _join_mountpoint_command( + cmd: list[str], + env_vars: list[tuple[str, str]], + *, + redact: bool = False, +) -> str: + joined_cmd = " ".join(shlex.quote(part) for part in cmd) + if not env_vars: + return joined_cmd + + env_parts = " ".join( + f"{name}={shlex.quote('REDACTED' if redact else value)}" for name, value in env_vars + ) + return f"{env_parts} {joined_cmd}" + + class MountPatternBase(BaseModel, abc.ABC): @abc.abstractmethod async def apply( @@ -425,24 +441,23 @@ async def apply( cmd.extend(["--prefix", mountpoint_config.prefix]) cmd.extend([bucket, sandbox_path_str(path)]) - env_parts: list[str] = [] + env_vars: list[tuple[str, str]] = [] access_key_id = mountpoint_config.access_key_id secret_access_key = mountpoint_config.secret_access_key session_token = mountpoint_config.session_token if access_key_id and secret_access_key: - env_parts.append(f"AWS_ACCESS_KEY_ID={shlex.quote(access_key_id)}") - env_parts.append(f"AWS_SECRET_ACCESS_KEY={shlex.quote(secret_access_key)}") + env_vars.append(("AWS_ACCESS_KEY_ID", access_key_id)) + env_vars.append(("AWS_SECRET_ACCESS_KEY", secret_access_key)) if session_token: - env_parts.append(f"AWS_SESSION_TOKEN={shlex.quote(session_token)}") + env_vars.append(("AWS_SESSION_TOKEN", session_token)) - joined_cmd = " ".join(shlex.quote(part) for part in cmd) - if env_parts: - joined_cmd = f"{' '.join(env_parts)} {joined_cmd}" + joined_cmd = _join_mountpoint_command(cmd, env_vars) + display_cmd = _join_mountpoint_command(cmd, env_vars, redact=True) result = await session.exec("sh", "-lc", joined_cmd, shell=False) if not result.ok(): raise MountCommandError( - command=joined_cmd, + command=display_cmd, stderr=result.stderr.decode("utf-8", errors="replace"), context={"bucket": bucket}, ) diff --git a/tests/sandbox/test_mount_patterns.py b/tests/sandbox/test_mount_patterns.py new file mode 100644 index 0000000000..faaba902d0 --- /dev/null +++ b/tests/sandbox/test_mount_patterns.py @@ -0,0 +1,25 @@ +from agents.sandbox.entries.mounts.patterns import _join_mountpoint_command + + +def test_join_mountpoint_command_redacts_aws_credentials_for_display() -> None: + env_vars = [ + ("AWS_ACCESS_KEY_ID", "AKIAEXAMPLE"), + ("AWS_SECRET_ACCESS_KEY", "super-secret"), + ("AWS_SESSION_TOKEN", "session-token"), + ] + + real_command = _join_mountpoint_command( + ["mount-s3", "bucket-name", "/workspace/mnt"], env_vars + ) + display_command = _join_mountpoint_command( + ["mount-s3", "bucket-name", "/workspace/mnt"], env_vars, redact=True + ) + + assert "super-secret" in real_command + assert "session-token" in real_command + assert "AKIAEXAMPLE" in real_command + assert "super-secret" not in display_command + assert "session-token" not in display_command + assert "AKIAEXAMPLE" not in display_command + assert display_command.count("REDACTED") == 3 + assert "mount-s3 bucket-name /workspace/mnt" in display_command