shellmcp: bwrap read-only sandbox with hidden paths
This commit is contained in:
+12
@@ -44,3 +44,15 @@ def run(command, cwd, timeout=60, limit=8000, prefix=()):
|
||||
out = b""
|
||||
head = f"timed out after {timeout} s, killed"
|
||||
return f"{head}\n{cut(out.decode(errors='replace'), limit)}"
|
||||
|
||||
def sandbox(cwd, hide=()):
|
||||
"""The bwrap argv prefix of --mode readonly."""
|
||||
argv = ["bwrap", "--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc",
|
||||
"--tmpfs", "/tmp", "--tmpfs", "/run", "--unshare-net", "--unshare-ipc",
|
||||
"--unshare-pid", "--die-with-parent", "--new-session"]
|
||||
for path in hide:
|
||||
if os.path.isdir(path):
|
||||
argv += ["--tmpfs", path]
|
||||
elif os.path.exists(path):
|
||||
argv += ["--ro-bind", "/dev/null", path]
|
||||
return argv + ["--chdir", cwd]
|
||||
|
||||
@@ -30,3 +30,17 @@ class Run(unittest.TestCase):
|
||||
|
||||
def test_no_stdin_and_no_pager(self):
|
||||
self.assertEqual(shellmcp.run("cat; echo $PAGER", HERE), "exit code 0\ncat\n")
|
||||
|
||||
@unittest.skipUnless(shutil.which("bwrap"), "needs bwrap")
|
||||
class Sandbox(unittest.TestCase):
|
||||
def test_readonly_cannot_write_and_hides_paths(self):
|
||||
with tempfile.TemporaryDirectory(dir=HERE) as d: # not /tmp: the sandbox replaces /tmp
|
||||
os.mkdir(os.path.join(d, "secret"))
|
||||
open(os.path.join(d, "secret", "key"), "w").close()
|
||||
prefix = shellmcp.sandbox(d, [os.path.join(d, "secret")])
|
||||
out = shellmcp.run("touch new; ls secret; echo > /tmp/x && echo tmp-ok", d,
|
||||
prefix=prefix)
|
||||
self.assertIn("Read-only file system", out)
|
||||
self.assertNotIn("key", out)
|
||||
self.assertIn("tmp-ok", out)
|
||||
self.assertFalse(os.path.exists(os.path.join(d, "new")))
|
||||
|
||||
Reference in New Issue
Block a user