/** 角色库根:v0 固定约定,不做配置。 */ export declare function roleLibraryRoot(): string; /** * 该 bot 自己的角色库子树(`<角色库根 realpath>/`)——沙盒 readWrite 白名单用。 * 任何一步不满足就返回 null:宁可不产生规则(角色系统不可用,机主看得见查得到), * 也不能把 readWrite 授到角色库之外。 * * 三道校验,缺一不可: * 1. appId 形状(单段目录名)——见 APP_ID_SEGMENT_RE。 * 2. 只 realpath 角色库根的**父目录**,然后 `botmux-roles` 与 `` 这**最后两段 * 各自 lstat、必须是真目录**。为什么这么切:沙盒两个引擎都按 canonical 路径匹配, * `$HOME` 本身是符号链接的机器(`/home/u` → `/data00/home/u` 这一类)不归一会静默 * fail-open——所以上层中间段必须 realpath、也允许它们是链接;但最后两段一旦跟链, * 「授权本 bot 的角色库」就变成「授权链接指向的任意目录」。 * 3. 因此末两段用 **lstat 而不是 stat/realpath**:若 `` 或 `/` 在 * spawn 前已被摆成指向 `~/.ssh`、`~/.botmux` 或**另一个 bot 的角色库**的符号链接, * 跟随解析会把链接目标当成本 bot 的子树直接授 rw —— 任意目录读写 + 跨 bot 越权。 * 末两段确定不是链接、上层已 realpath,返回值天然是 canonical 路径,调用方不得再 * realpath(再跟一次就把这道校验作废了)。 * * 明确不在本函数(也不在本文件)射程内的两件事,都需要宿主级写权限,而拿到宿主级写 * 权限的人本来就能直接改 bots.json 关掉沙盒 —— 且这两条对策略里**每一条**路径规则 * (workingDir、botHome、cliDataPaths…全都在同一时点做一次存在性检查)都同样成立, * 只为这一条规则加固属于安全戏剧: * - TOCTOU:lstat 之后、真正 spawn/bind 之前把目录换成符号链接。路径型沙盒(Seatbelt * 吃路径字符串、bwrap 吃 bind 源)无法靠持 fd 关闭这个窗口。 * - mount point:末段是 bind/FUSE 挂载点时仍是「真目录」,能把挂载目标整棵授出去。 * * 另一件既有遗留:大小写不敏感卷上两个仅大小写不同的 appId 指向同一目录。那是角色库 * 按 appId 分目录这个布局本身的性质(不开沙盒也一样共享),要治得在 bot 配置加载期按 * 文件系统身份拒绝碰撞。 */ export declare function roleLibrarySubtree(appId: string, rootOverride?: string): string | null; /** * `botmux role switch` 的目标目录硬校验(调用方是模型,不可信): * realpath 归一化(防 ../ 与符号链接逃逸)→ 必须位于角色库根之下 * (文件系统身份 dev+ino 比较,防前缀兄弟目录与大小写变体绕过)→ 必须是已存在的目录。 * * 传了 `ownAppId` 时**收窄到该 bot 自己的子树** `<角色库根>/`:不传等同旧行为 * (只 pin 全局根)。收窄的理由是不收窄就能切进**别的 bot 的角色目录**——`/cd` 路由随后 * 把 `ds.workingDir` 钉过去(`dashboard-ipc-server.ts`),而 `ds.workingDir` 在 fs-policy * 里**永远拿 readWrite**,于是那个 bot 的沙盒会话拿到对方角色目录(含 `users/<别人 * openId>/` 私有角色)的 readWrite——rw 从 workingDir 那条腿来,不是从角色库专用 grant * (后者按本 bot appId keying,对错位目标返 null)。 * * **FAIL-CLOSED(codex delta review)**:传了 `ownAppId` 但 `<根>/` 不是真目录 * (旧 runbook 用人类 slug 命名这一层,或被摆成符号链接)时**直接拒绝** * (`own_role_library_missing`),**不回落全局根**。曾经的「回落全局根」是 fail-open: * 存量非 appId 布局下它让 `botmux role switch` 继续能切进任意 bot 的库内目录并经 * workingDir 拿 rw,正是收窄要堵的越权。fail-closed 的代价是存量非 appId 部署在迁移 * (`docs/roles/deploy-runbook.md` §8:把这层目录名改成 appId)之前切不动角色——但 * 沙盒下角色系统本就 EPERM 不可用,fail-closed 不额外损失可用功能;非沙盒部署迁移一次 * 即恢复。人工 IM `/cd` 不走本校验(走 `validateWorkingDir`),不受影响。 */ export declare function validateRoleLibraryPath(input: string, rootOverride?: string, ownAppId?: string): { ok: true; resolvedPath: string; } | { ok: false; error: string; }; //# sourceMappingURL=role-library.d.ts.map