Files
MailUI4Agents/plugins/pi-mail-bridge/test/workspace-plausible.test.mjs
JianFeeeee 141003dce4 fix(pi-bridge): 地址里的 path 必须像工作目录 —— worker 不再被起在 /home 里
网关侧已在地址受理处拒收 `/home` 这类 path(ad1f3f1)。这里是**同一规则的第二道**,
因为它落在唯一的 choke point 上(`resolveWorkspaceCwd`,所有 cwd 都从这里出):

旧实现只判"存在且是目录",而 `/home` **存在**、也是目录 ⇒ 一路放行 ✗。
后果(2026-09-15 实测):worker 被起在 `/home`(日志 `新建 pi 会话 …(cwd=/home)`),
而它的沙箱 rw 里根本没有 `/home` —— 它在自己"工作目录"里连文件都写不了;
更糟的是它发出去的信继续带 `/home`,把污染沿线索传下去。

新增 isPlausibleWorkspace(与 Go 侧 repo.IsPlausibleWorkspace 同一套规则):
绝对路径 + 深度≥2(顶层挂载点不是干活的地方)+ 不含隐藏段(`.pi`/`.cache` 是缓存与会话存储)。
**只判地址来的值**,不判兜底目录本身 —— `/root/.pi/mail-sessions/<会话>` 这类兜底
正是"没有工作目录"的表达,判它会把正常兜底也拦掉。

判据(5 条,两侧都写 + 变异验证过):实测污染过的形状必须拒;真实工作目录
(含尚未创建的 /tmp/remotebot-ws)必须放行。去掉校验后判据变红 ✓。
桥的正式套件 514 项全绿 ✓。

未部署:pi 桥要 `deploy/redeploy-plugin.sh` 才生效,而那会重启 worker 池
(硬杀在跑的回合),所以等空闲窗口再做 —— 服务端那道闸已经在线,这条是兜底。
2026-09-15 12:32:50 +08:00

61 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 地址里的 path 必须像工作目录 —— 桥侧的第二道闸(2026-09-15)。
*
* 背景(用户报的严重错投):
* 客户端把收件人的 path 填成 `/home` → 会话 workspace=/home →
* 桥按来信的 to_workspace 起 worker(实测日志 `新建 pi 会话 …(cwd=/home)`)→
* 那个 worker 发的信继续带 /home → 建议接口把它学成第一条候选 → 自我强化。
*
* 旧实现的漏洞就一行:只判"存在且是目录",而 `/home` **存在**、也是目录 ⇒ 放行 ✗。
* 而且那个 worker 的沙箱 rw 里没有 `/home`,它连自己"工作目录"里都写不了文件。
*
* 网关侧已在地址受理处拒收这类 path(400);这里钉住**同一规则的第二道**,
* 因为旧数据、别的客户端、以后新增的入口都可能再送进来。
* 判据两侧都写:实测污染过的形状必须被拒,真实工作目录必须放行
* (只验一侧的话,"永远退回兜底"也能骗过坏样本那一侧)。
*/
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { isPlausibleWorkspace, resolveWorkspaceCwd } from '../lib/workspace.js';
test('isPlausibleWorkspace:实测污染过的形状必须被拒', () => {
for (const bad of ['/home', '/root', '/tmp', '/root/.pi/mail-sessions/8f056b73', 'rel/path', '', ' ']) {
assert.equal(isPlausibleWorkspace(bad), false, `${JSON.stringify(bad)} 不该被当成工作目录`);
}
});
test('isPlausibleWorkspace:真实工作目录必须放行(含尚未创建的)', () => {
for (const good of [
'/home/program/agentmail',
'/home/newqqagent',
'/tmp/remotebot-ws', // 注册时自报、还没建的目录
'/home/program/llmsproxy',
]) {
assert.equal(isPlausibleWorkspace(good), true, `${good} 应被当成工作目录`);
}
});
test('resolveWorkspaceCwd:/home 一律退回兜底目录且标记 grouped=false', () => {
const fb = '/root/.agentmail-pi/workspaces/sess-1';
const got = resolveWorkspaceCwd('/home', fb);
assert.equal(got.cwd, fb, '/home 必须被换成兜底目录(它存在,旧实现会照用)');
assert.equal(got.grouped, false, 'grouped=false 才能让上层知道"这不是它自己的目录"');
});
test('resolveWorkspaceCwd:真实且存在的目录照用', () => {
const dir = mkdtempSync(join(tmpdir(), 'pi-ws-'));
const got = resolveWorkspaceCwd(dir, '/root/.agentmail-pi/workspaces/sess-2');
assert.equal(got.cwd, dir);
assert.equal(got.grouped, true);
});
test('resolveWorkspaceCwd:不存在但像工作目录的 → 退回兜底(原行为不变)', () => {
const fb = '/root/.agentmail-pi/workspaces/sess-3';
const got = resolveWorkspaceCwd('/home/program/不存在的目录-xyz', fb);
assert.equal(got.cwd, fb);
assert.equal(got.grouped, false);
});