/** * worker 的**启动方式**按档位定:plan/workspace 套 Landlock 边界,full 档不套。 * * # 为什么这条判据必须是"行为"而不是"文件里有那行字" * * 接线错的方式很安静: * - 忘了把会话工作区放进 `--rw` ⇒ **界内也写不了**,worker 回合中途报 EACCES; * - 把 full 档也套上 ⇒ 发件人声明了全权却被内核拦,行为与档位表相反; * - 拿不到 cwd 时"猜一个目录" ⇒ 沙箱放行的是 A、worker 落在 B,最难查的那一类。 * 所以这里直接调 `workerLaunch`,断言它给出的**命令行**是什么。 */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { DEFAULT_SANDBOX_BIN, sandboxBin, sandboxWritePaths, workerLaunch } from '../lib/sandbox.js'; // 假文件系统:只认列出来的路径,避免判据依赖本机装了什么。 const fsWith = (paths) => (p) => paths.includes(p); const NODE = '/usr/bin/node'; const WORKER = '/opt/agentmail/plugins/pi-mail-bridge/current/src/worker.mjs'; const CWD = '/home/program/agentmail'; const HOME = '/root'; const ENV = { TMPDIR: '/tmp', HOME }; test('★ workspace 档:套沙箱,且会话工作区在 rw 里', () => { const exists = fsWith([DEFAULT_SANDBOX_BIN, CWD, '/tmp', `${HOME}/.pi/agent/sessions`, `${HOME}/.agentmail`, '/dev/null']); const l = workerLaunch({ mode: 'workspace', cwd: CWD, nodePath: NODE, workerPath: WORKER, env: ENV, exists }); assert.equal(l.sandboxed, true, `workspace 档应当套沙箱(reason=${l.reason})`); assert.equal(l.cmd, DEFAULT_SANDBOX_BIN); // 会话工作区必须在 rw 里 —— 少了它,界内也写不了 const rwIdx = l.argv.indexOf(CWD); assert.ok(rwIdx > 0 && l.argv[rwIdx - 1] === '--rw', `--rw 里必须有会话工作区:${l.argv.join(' ')}`); // /dev/null 用 --rw-file(只放行写这个设备),否则 `cmd 2>/dev/null` 全线失败 const dnIdx = l.argv.indexOf('/dev/null'); assert.ok(dnIdx > 0 && l.argv[dnIdx - 1] === '--rw-file', `--rw-file 里必须有 /dev/null:${l.argv.join(' ')}`); // 命令要在 `--` 之后,且是 node + worker const sep = l.argv.indexOf('--'); assert.deepEqual(l.argv.slice(sep + 1), [NODE, WORKER], '`--` 之后应当是 node 与 worker 脚本'); }); test('★ full 档:不套(发件人已声明全权)', () => { const exists = fsWith([DEFAULT_SANDBOX_BIN, CWD, '/tmp', '/dev/null']); const l = workerLaunch({ mode: 'full', cwd: CWD, nodePath: NODE, workerPath: WORKER, env: ENV, exists }); assert.equal(l.sandboxed, false); assert.deepEqual(l.argv, [WORKER], 'full 档必须直接起 worker'); assert.match(l.reason, /full/); }); test('★ 拿不到会话工作区时不套,且理由说得出(不猜目录)', () => { const exists = fsWith([DEFAULT_SANDBOX_BIN, '/tmp', '/dev/null']); const l = workerLaunch({ mode: 'workspace', cwd: '', nodePath: NODE, workerPath: WORKER, env: ENV, exists }); assert.equal(l.sandboxed, false, '没有 cwd 时必须退回不套 —— 猜一个 rw 会让界内也写不了'); assert.match(l.reason, /工作区/); }); test('没有沙箱二进制时不套(但理由是明确的)', () => { const l = workerLaunch({ mode: 'workspace', cwd: CWD, nodePath: NODE, workerPath: WORKER, env: ENV, exists: fsWith([CWD]) }); assert.equal(l.sandboxed, false); assert.match(l.reason, /am-sandbox/); }); test('AGENTMAIL_PI_SANDBOX:显式关掉 / 显式指路', () => { const exists = fsWith([DEFAULT_SANDBOX_BIN, '/var/tmp/my-sandbox', CWD, '/tmp', '/dev/null']); // 显式关:即使默认装好了也不套 assert.equal(sandboxBin({ AGENTMAIL_PI_SANDBOX: 'off' }, exists), ''); assert.equal(sandboxBin({ AGENTMAIL_PI_SANDBOX: '0' }, exists), ''); assert.equal(sandboxBin({ AGENTMAIL_PI_SANDBOX: ' ' }, exists), ''); // 显式指路:用给的那个 assert.equal(sandboxBin({ AGENTMAIL_PI_SANDBOX: '/var/tmp/my-sandbox' }, exists), '/var/tmp/my-sandbox'); // 显式给了但不存在 ⇒ 不套(由调用方把理由打进日志,不静默裸跑) assert.equal(sandboxBin({ AGENTMAIL_PI_SANDBOX: '/nope' }, exists), ''); // 没给 ⇒ 看默认位置 assert.equal(sandboxBin({}, exists), DEFAULT_SANDBOX_BIN); assert.equal(sandboxBin({}, fsWith([])), ''); }); test('rw 只收**存在**的路径(am-sandbox 对不存在的 rw fail closed)', () => { const exists = fsWith(['/tmp', '/dev/null']); // sessions/config 都不在 const { dirs, files } = sandboxWritePaths({ cwd: '/nope', env: ENV, exists, home: HOME }); assert.deepEqual(dirs, ['/tmp'], '不存在的目录不该进 rw'); assert.deepEqual(files, ['/dev/null']); // 去重:同一个目录给两次会让 argv 里出现重复的 --rw(无害但说明来源没收敛) const dup = sandboxWritePaths({ cwd: '/tmp', env: { TMPDIR: '/tmp' }, exists, home: HOME }); assert.equal(dup.dirs.filter((d) => d === '/tmp').length, 1, 'rw 目录要去重'); });