diff --git a/studio/backend/core/inference/tools.py b/studio/backend/core/inference/tools.py index 299c3234b5..ea01d74947 100644 --- a/studio/backend/core/inference/tools.py +++ b/studio/backend/core/inference/tools.py @@ -158,8 +158,11 @@ _FIND_EXEC_FLAGS = frozenset({"-exec", "-execdir", "-ok", "-okdir"}) # ``~/.cache/``) MUST stay out of this list — those still flow through. _HOME_RELATIVE_SENSITIVE = ( # SSH private keys (config / known_hosts intentionally allowed) - r"\.ssh/id_rsa", r"\.ssh/id_ed25519", r"\.ssh/id_ecdsa", - r"\.ssh/id_dsa", r"\.ssh/identity", + r"\.ssh/id_rsa", + r"\.ssh/id_ed25519", + r"\.ssh/id_ecdsa", + r"\.ssh/id_dsa", + r"\.ssh/identity", # Cloud provider credentials r"\.aws/credentials", r"\.docker/config\.json", diff --git a/studio/backend/tests/test_sandbox_hardening.py b/studio/backend/tests/test_sandbox_hardening.py index eef45736d3..5eaca58657 100644 --- a/studio/backend/tests/test_sandbox_hardening.py +++ b/studio/backend/tests/test_sandbox_hardening.py @@ -56,6 +56,7 @@ SUDO = "s" + "u" + "do" # Patch A — concatenated + f-string path resolution in open() # --------------------------------------------------------------------------- + class TestPatchA_DynamicPaths: @pytest.mark.parametrize( "code", @@ -68,7 +69,7 @@ class TestPatchA_DynamicPaths: "open('/etc' + '/' + 'shadow')", # F-string with a literal interpolation "open(f'/etc/{\"shadow\"}')", - "open(f'/{\"etc\"}/{\"shadow\"}')", + 'open(f\'/{"etc"}/{"shadow"}\')', # Same surface via io.open / pathlib.Path.open "import io; io.open('/etc/' + 'shadow')", ], @@ -114,6 +115,7 @@ class TestPatchA_DynamicPaths: # Patch B — sensitive paths in bash (direct helper API) # --------------------------------------------------------------------------- + class TestPatchB_FindSensitivePathsHomeAnchored: @pytest.mark.parametrize( "cmd", @@ -173,9 +175,9 @@ class TestPatchB_FindSensitivePathsHomeAnchored: ], ) def test_legitimate_allowed(self, cmd): - assert not _find_sensitive_paths(cmd), ( - f"expected to allow (would dumbify tool calling): {cmd!r}" - ) + assert not _find_sensitive_paths( + cmd + ), f"expected to allow (would dumbify tool calling): {cmd!r}" class TestPatchB_FindSensitivePathsAbsolute: @@ -220,9 +222,9 @@ class TestPatchB_FindSensitivePathsAbsolute: ], ) def test_legitimate_absolute_allowed(self, cmd): - assert not _find_sensitive_paths(cmd), ( - f"expected to allow (would dumbify tool calling): {cmd!r}" - ) + assert not _find_sensitive_paths( + cmd + ), f"expected to allow (would dumbify tool calling): {cmd!r}" class TestPatchB_PythonShellExec: @@ -261,27 +263,28 @@ class TestPatchB_PythonShellExec: ], ) def test_legitimate_allowed(self, code): - assert not _is_blocked(code), ( - f"expected to allow (would dumbify tool calling): {code!r}" - ) + assert not _is_blocked( + code + ), f"expected to allow (would dumbify tool calling): {code!r}" # --------------------------------------------------------------------------- # Patch D — eval / exec body recursion # --------------------------------------------------------------------------- + class TestPatchD_EvalExecLiteralPayload: @pytest.mark.parametrize( "code", [ # Shell-escape inside an exec payload f"exec(\"import os; os.system('{SUDO} whoami')\")", - f"exec('import subprocess; subprocess.run([\"{SUDO}\", \"id\"])')", + f'exec(\'import subprocess; subprocess.run(["{SUDO}", "id"])\')', # Sensitive-file open inside exec payload "exec(\"open('/etc/shadow').read()\")", "exec(\"with open('/etc/passwd') as f: print(f.read())\")", # Nested - f"exec(\"exec(\\\"import os; os.system('{SUDO} id')\\\")\")", + f'exec("exec(\\"import os; os.system(\'{SUDO} id\')\\")")', ], ) def test_literal_attack_payload_blocked(self, code): @@ -301,9 +304,9 @@ class TestPatchD_EvalExecLiteralPayload: ], ) def test_legitimate_eval_exec_allowed(self, code): - assert not _is_blocked(code), ( - f"expected to allow (would dumbify tool calling): {code!r}" - ) + assert not _is_blocked( + code + ), f"expected to allow (would dumbify tool calling): {code!r}" class TestPatchD_EvalExecDynamicPayload: @@ -340,6 +343,7 @@ class TestPatchD_NestedDepthCap: # blocks. # --------------------------------------------------------------------------- + class TestCrossCuttingNoRegression: @pytest.mark.parametrize( "code", @@ -374,6 +378,6 @@ class TestCrossCuttingNoRegression: ], ) def test_preexisting_allowed_still_pass(self, code): - assert not _is_blocked(code), ( - f"REGRESSION: pre-existing pass-through now blocked: {code!r}" - ) + assert not _is_blocked( + code + ), f"REGRESSION: pre-existing pass-through now blocked: {code!r}"