#!/usr/bin/env python3 # 档案 18/17 P1(H2):把平台策略文件在实例内设为只读(bwrap --ro-bind-try) # 用法: python3 patch-b1-write-protect.py /opt/dshs import io, os, sys root = sys.argv[1] if len(sys.argv) > 1 else '.' p = os.path.join(root, 'src/supervisor/orchestrator.ts') s = io.open(p, encoding='utf-8').read() # 1) spawnAsUser 增加 role 形参(用于定位 profile 目录名) old_sig = """ private async spawnAsUser( userId: string, command: string, args: string[], options: { cwd: string; env: Record }, ): Promise<{ child: ChildProcess; unit?: string }> {""" new_sig = """ private async spawnAsUser( userId: string, command: string, args: string[], options: { cwd: string; env: Record }, role: InstanceRole = 'main', ): Promise<{ child: ChildProcess; unit?: string }> {""" assert old_sig in s, 'sig' s = s.replace(old_sig, new_sig, 1) # 2) 调用点传入 role old_call = """ const { child, unit } = await this.spawnAsUser(userId, command, [...args, ...launchArgs], { cwd: folder, env })""" new_call = """ const { child, unit } = await this.spawnAsUser(userId, command, [...args, ...launchArgs], { cwd: folder, env }, role)""" assert old_call in s, 'call' s = s.replace(old_call, new_call, 1) # 3) 在 --bind root root 之后追加平台策略文件的只读覆盖 old_bind = """ '--bind', tmpDir, '/tmp', '--bind', root, root, '--unshare-pid',""" new_bind = """ '--bind', tmpDir, '/tmp', '--bind', root, root, // 2026-09-11(档案 18 v3 收尾 / 档案 17 §P1):平台策略文件在实例内**只读**。 // 威胁模型:用户可把 /home/profiles/web 加为工作区,随后用 bash 直接改写 // cordis.patch.yml(去掉平台段 → 恢复全盘 picker)或 package.json(挂任意 bundle)→ // 属"绕过平台策略"(跨租户仍不成立,uid/bwrap 隔离不变)。 // 只读三个文件;**不动 cordis.yml**——实测 dsh 启动时会写它(01:16:22),ro 会导致启动异常。 // 注意:必须放在 '--bind root root' **之后**(bwrap 后写覆盖前写)。 ...(() => { const profileDir = join(homeRoot(root), 'profiles', role === 'main' ? 'web' : 'headless') const protectedFiles = ['cordis.patch.yml', 'package.json', 'pnpm-lock.yaml'] const out: string[] = [] for (const name of protectedFiles) { const file = join(profileDir, name) out.push('--ro-bind-try', file, file) } return out })(), '--unshare-pid',""" assert old_bind in s, 'bind' s = s.replace(old_bind, new_bind, 1) io.open(p, 'w', encoding='utf-8', newline='').write(s) print('B1 补丁脚本已生成')