"""宿主级沙箱路由单测:严格 executor 认领 + 配置/profile 文件。 只用 SQLite 测试库与假 runner,不跑真实 codex/Docker。 覆盖:claim_executor_only 开启时 _ready_tasks 只返回 executor == 'local' 的任务(NULL 与 docker 都排除),且 _claim 对 NULL 任务返回 False、对 local 任务返回 True; host-config-host.json 的 sandbox 确为 workspace-write; codex-host profile 含 userns 与 mount,且不含 deny mount。 """ import json import os import sys import tempfile import unittest from pathlib import Path try: from cloud.server import init_db from cloud.worker import Worker except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.server import init_db from cloud.worker import Worker import sqlite3 def _seed(db_path): init_db(db_path) conn = sqlite3.connect(db_path) conn.execute( "INSERT INTO projects (id, name) VALUES ('p1', 'p1')") conn.execute( "INSERT INTO tasks (id, project_id, prompt, status, executor)" " VALUES ('null-task', 'p1', 'hi', 'queued', NULL)") conn.execute( "INSERT INTO tasks (id, project_id, prompt, status, executor)" " VALUES ('docker-task', 'p1', 'hi', 'queued', 'docker')") conn.execute( "INSERT INTO tasks (id, project_id, prompt, status, executor)" " VALUES ('local-task', 'p1', 'hi', 'queued', 'local')") conn.commit() conn.close() class StrictClaimTestCase(unittest.TestCase): def test_strict_worker_only_takes_local(self): tmp = tempfile.TemporaryDirectory() db_path = os.path.join(tmp.name, "control.db") _seed(db_path) worker = Worker(db_path, "/fake/host.py", env={"XCM_API_KEY": "test-placeholder"}, claim_executor_only=True) ready = worker._ready_tasks() self.assertEqual([t["id"] for t in ready], ["local-task"]) # CAS 层同样严格:NULL 任务抢不到,local 任务抢得到。 self.assertFalse(worker._claim("null-task", executor="local")) self.assertTrue(worker._claim("local-task", executor="local")) tmp.cleanup() def test_default_worker_still_takes_null(self): tmp = tempfile.TemporaryDirectory() db_path = os.path.join(tmp.name, "control.db") _seed(db_path) worker = Worker(db_path, "/fake/host.py", env={"XCM_API_KEY": "test-placeholder"}) ready = worker._ready_tasks() ids = [t["id"] for t in ready] self.assertIn("null-task", ids) self.assertIn("local-task", ids) self.assertNotIn("docker-task", ids) tmp.cleanup() class HostConfigProfileTestCase(unittest.TestCase): def test_host_config_uses_workspace_write(self): repo_root = Path(__file__).resolve().parents[2] cfg = json.loads((repo_root / "host-config-host.json").read_text()) self.assertEqual(cfg["sandbox"], "workspace-write") self.assertEqual(cfg["state"], "work/host-state-host.json") self.assertEqual(cfg["codex_home"], "/var/lib/codex-app-server/codex-home") def test_codex_host_profile_allows_bwrap(self): repo_root = Path(__file__).resolve().parents[2] text = (repo_root / "deploy" / "apparmor" / "codex-host" ).read_text() self.assertIn("userns,", text) # bwrap 需要 mount 与 pivot_root:profile 必须允许, # 不能像 docker-codex 那样 deny。 self.assertIn("mount,", text) self.assertIn("pivot_root,", text) self.assertNotIn("deny mount,", text) self.assertNotIn("deny umount,", text) # bwrap 还需要 userns 内的 capability(实测:deny # capability sys_admin 会让 bwrap 在 userns 创建处失败, # 而 capability, 允许则成功;codexsvc 本身无宿主 # capability,所以这里允许的只是 userns 内的假 root # capability,不授予宿主特权)。 self.assertIn("capability,", text) self.assertNotIn("deny capability sys_admin,", text) # bwrap 还需要 netlink(在新网络命名空间里拉起 loopback; # 缺了会报 Failed to bind NETLINK_ROUTE socket,且任何 # 工具都跑不起来)。注意 AppArmor 里 deny 优先于 allow, # 所以 raw 的 deny 必须写明 inet/inet6,不能写裸的 # deny network raw(那样会连 netlink raw 一起挡掉, # 已用隔离 netlink bind 实测验证)。 self.assertIn("network netlink raw,", text) self.assertIn("deny network inet raw,", text) self.assertNotIn("deny network raw,", text) if __name__ == "__main__": unittest.main()