mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-06 11:13:38 +00:00
refactor: replace podman-seccomp.json with minimal cap_add, harden bwrap, add sandbox tests
This commit is contained in:
parent
a8707ca8f6
commit
cef0f3f988
@ -4,9 +4,13 @@ x-common-config: &common-config
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ~/.nanobot:/home/nanobot/.nanobot
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
- seccomp=./podman-seccomp.json
|
||||
- seccomp=unconfined
|
||||
|
||||
services:
|
||||
nanobot-gateway:
|
||||
|
||||
@ -25,7 +25,7 @@ def _bwrap(command: str, workspace: str, cwd: str) -> str:
|
||||
optional = ["/bin", "/lib", "/lib64", "/etc/alternatives",
|
||||
"/etc/ssl/certs", "/etc/resolv.conf", "/etc/ld.so.cache"]
|
||||
|
||||
args = ["bwrap"]
|
||||
args = ["bwrap", "--new-session", "--die-with-parent"]
|
||||
for p in required: args += ["--ro-bind", p, p]
|
||||
for p in optional: args += ["--ro-bind-try", p, p]
|
||||
args += [
|
||||
|
||||
1129
podman-seccomp.json
1129
podman-seccomp.json
File diff suppressed because it is too large
Load Diff
105
tests/tools/test_sandbox.py
Normal file
105
tests/tools/test_sandbox.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""Tests for nanobot.agent.tools.sandbox."""
|
||||
|
||||
import shlex
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.agent.tools.sandbox import wrap_command
|
||||
|
||||
|
||||
def _parse(cmd: str) -> list[str]:
|
||||
"""Split a wrapped command back into tokens for assertion."""
|
||||
return shlex.split(cmd)
|
||||
|
||||
|
||||
class TestBwrapBackend:
|
||||
def test_basic_structure(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
result = wrap_command("bwrap", "echo hi", ws, ws)
|
||||
tokens = _parse(result)
|
||||
|
||||
assert tokens[0] == "bwrap"
|
||||
assert "--new-session" in tokens
|
||||
assert "--die-with-parent" in tokens
|
||||
assert "--ro-bind" in tokens
|
||||
assert "--proc" in tokens
|
||||
assert "--dev" in tokens
|
||||
assert "--tmpfs" in tokens
|
||||
|
||||
sep = tokens.index("--")
|
||||
assert tokens[sep + 1:] == ["sh", "-c", "echo hi"]
|
||||
|
||||
def test_workspace_bind_mounted_rw(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
result = wrap_command("bwrap", "ls", ws, ws)
|
||||
tokens = _parse(result)
|
||||
|
||||
bind_idx = [i for i, t in enumerate(tokens) if t == "--bind"]
|
||||
assert any(tokens[i + 1] == ws and tokens[i + 2] == ws for i in bind_idx)
|
||||
|
||||
def test_parent_dir_masked_with_tmpfs(self, tmp_path):
|
||||
ws = tmp_path / "project"
|
||||
result = wrap_command("bwrap", "ls", str(ws), str(ws))
|
||||
tokens = _parse(result)
|
||||
|
||||
tmpfs_indices = [i for i, t in enumerate(tokens) if t == "--tmpfs"]
|
||||
tmpfs_targets = {tokens[i + 1] for i in tmpfs_indices}
|
||||
assert str(ws.parent) in tmpfs_targets
|
||||
|
||||
def test_cwd_inside_workspace(self, tmp_path):
|
||||
ws = tmp_path / "project"
|
||||
sub = ws / "src" / "lib"
|
||||
result = wrap_command("bwrap", "pwd", str(ws), str(sub))
|
||||
tokens = _parse(result)
|
||||
|
||||
chdir_idx = tokens.index("--chdir")
|
||||
assert tokens[chdir_idx + 1] == str(sub)
|
||||
|
||||
def test_cwd_outside_workspace_falls_back(self, tmp_path):
|
||||
ws = tmp_path / "project"
|
||||
outside = tmp_path / "other"
|
||||
result = wrap_command("bwrap", "pwd", str(ws), str(outside))
|
||||
tokens = _parse(result)
|
||||
|
||||
chdir_idx = tokens.index("--chdir")
|
||||
assert tokens[chdir_idx + 1] == str(ws.resolve())
|
||||
|
||||
def test_command_with_special_characters(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
cmd = "echo 'hello world' && cat \"file with spaces.txt\""
|
||||
result = wrap_command("bwrap", cmd, ws, ws)
|
||||
tokens = _parse(result)
|
||||
|
||||
sep = tokens.index("--")
|
||||
assert tokens[sep + 1:] == ["sh", "-c", cmd]
|
||||
|
||||
def test_system_dirs_ro_bound(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
result = wrap_command("bwrap", "ls", ws, ws)
|
||||
tokens = _parse(result)
|
||||
|
||||
ro_bind_indices = [i for i, t in enumerate(tokens) if t == "--ro-bind"]
|
||||
ro_targets = {tokens[i + 1] for i in ro_bind_indices}
|
||||
assert "/usr" in ro_targets
|
||||
|
||||
def test_optional_dirs_use_ro_bind_try(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
result = wrap_command("bwrap", "ls", ws, ws)
|
||||
tokens = _parse(result)
|
||||
|
||||
try_indices = [i for i, t in enumerate(tokens) if t == "--ro-bind-try"]
|
||||
try_targets = {tokens[i + 1] for i in try_indices}
|
||||
assert "/bin" in try_targets
|
||||
assert "/etc/ssl/certs" in try_targets
|
||||
|
||||
|
||||
class TestUnknownBackend:
|
||||
def test_raises_value_error(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
with pytest.raises(ValueError, match="Unknown sandbox backend"):
|
||||
wrap_command("nonexistent", "ls", ws, ws)
|
||||
|
||||
def test_empty_string_raises(self, tmp_path):
|
||||
ws = str(tmp_path / "project")
|
||||
with pytest.raises(ValueError):
|
||||
wrap_command("", "ls", ws, ws)
|
||||
Loading…
x
Reference in New Issue
Block a user