Files
ycode/docs/沙盒只读文件桥接入-2026-10-18.md
T

99 lines
8.9 KiB
Markdown
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.
# 沙盒只读文件桥接入(2026-10-18)
> 后续增量:宿主产物提交/补偿/恢复已在组件 0.5.0 接入[原生写回事务](沙盒原生写回事务-2026-10-18.md)。本文件保留读取桥交付时的范围与证据;读取桥本身仍只读,宿主原生事务不等于完整文件写入调用桥已经完成。
## 当前交付
在[首批接入](沙盒完整接入首批实施-2026-10-18.md)上,P1/P3 已新增真实的工作区只读文件桥:模型可显式选择,Node 同步/异步客户端通过已绑定 Job 的 stdio 调用原生文件读取,操作结果在回复前落盘,断线后可以查询。完整目标继续按[计划](沙盒完整接入计划-2026-10-18.md)及[验收矩阵](沙盒接入验收矩阵-2026-10-18.md)推进。
本批另修复了 AppContainer 私有 TEMP 目录,并补充[显式 Node 同步子进程适配器](沙盒Node子进程与临时目录修复-2026-10-18.md)。该适配器不依赖读取桥,也不增加宿主文件/网络权限。
这是一套 YCode 自有实现。本批没有新增 WorkBuddy 取证,也没有原版等价结论。`filesystem-broker` 整体能力仍不可用;独立的 `workspace-read-bridge` 只声明下述读取子集。
## 调用与执行点
```json
{
"command": "node inspect.cjs",
"runtimes": ["node"],
"bridge": "workspace-read"
}
```
```js
const workspace = require(process.env.YCODE_SANDBOX_BRIDGE).connectSync()
try {
const bytes = workspace.readFileSync('src/example.ts')
const stat = workspace.statSync('src/example.ts')
const entries = workspace.readdirSync('src')
console.log(bytes.length, stat.kind, entries.length)
} finally {
workspace.close()
}
```
异步使用 `connect()` 的 `readFile`、`stat`、`readdir`、`query` 和 `close`。一个 Job 同时只应有一个 stdio 客户端;客户端独占该进程 stdin,不能同时作为交互输入。它不覆盖 Node 标准 `fs`,也不会将普通绝对路径自动映射到宿主。`stat`/`readdir` 返回桥的有界 JSON 元数据,不声称兼容完整 Node Stats/Dirent。
| 源码 | 职责 |
| --- | --- |
| `native/sandbox/filesystem-read.h` | 宿主专用原生读取器;持有祖先/目标句柄,校验卷、工作区身份和最终路径;读取、元数据、句柄目录枚举 |
| `native/sandbox/runner.cpp` | 拒绝容器内部调用宿主读取器;继续保持 AppContainer/Job 与默认禁网;转发有界 stdio 回复 |
| `native/sandbox/node-bridge.cjs` | 自有同步/异步客户端、分块摘要检查、异步队列和退出清理 |
| `src/main/sandbox/filesystem-reader.ts` | 策略及拒绝路径、实际原生调用、取消/时限、报告解析与最终名称复核 |
| `src/main/sandbox/operation-broker.ts` | 严格帧、按 Job 绑定的归属、请求/快照上限、状态日志、结果查询与协议失败清理 |
| `src/main/sandbox/maintenance.ts` | 重启/断线后的持久结果查询;未完成记录返回 unknown |
客户端随原生组件构建、打包;manifest 记录 runner/header/client 源码摘要及客户端摘要。选择文件桥时,客户端摘要不符会拒绝启动,不查找其他宿主模块。
## 实际边界
1. 当前仅支持 Windows x64、本地固定 NTFS 卷。读取器从卷根开始逐级打开目录,保留整个祖先链,并拒绝 reparse point、设备和多重硬链接。句柄只共享读取,已有可写句柄导致 EBUSY;读取期间禁止对应对象的改写、删除或重命名,不放宽共享模式重试。
2. 每个 Job 固定工作区卷/文件 ID。每次调用重新核对根身份;同路径替换成另一个目录不会取得原授权。目标最终规范名称来自句柄,与根的 volume GUID 路径比较,并再次经过敏感名称/拒绝前缀规则。宿主状态目录和副本排除目录不向桥开放。
3. 相对路径严格拒绝 `..`、空组件、绝对路径、ADS/UNC/设备、尾部空格/点及敏感文件。目录列表会过滤敏感项和链接。普通 `fs` 及原生后代仍受原有 AppContainer 权限约束。
4. 调用归属来自主进程保存的 session/run/tool-call、策略版本/内容指纹及本次 sandboxRunId。子进程不能提交替代归属。该权限范围属于整个 Job;它没有独立的逐后代 PID 认证,不能据此声称更细粒度的进程规则已完成。
5. 单次读取先在原生句柄下取得完整有界内容,再以 16 KiB 块回复。后续块来自同一只读内存快照,宿主外部编辑不会拼接出混合版本。每块核对身份/摘要,客户端完成后再核对完整 SHA-256。
6. 桥不实现宿主写入/删除/联网。普通命令产物仍由副本差异、审批和原有事务写回;原有事务的宿主目标约束没有因本批读取器而自动升级。
| 限制 | 当前值 |
| --- | --- |
| 请求帧/回复值 | 8 KiB / 24 KiB |
| 每 Job 并发/总请求 | 8 / 1024 |
| 异步客户端总排队 | 32,向宿主最多发送 8 项 |
| 单文件读取 | min(策略 fileBytes, 8 MiB) |
| 读取快照 | 最多 8 个,总容量 min(策略 bytes, 16 MiB),空闲 60 秒失效 |
| 目录枚举 | 最多 128 个原始条目;序列化超限则拒绝,不截断后冒充完整结果 |
| 原生读取器/代理截止 | 4 秒 / 5 秒;原生另监控父进程退出 |
## 操作生命周期和查询
请求包含 version、UUID requestId、operation、args。未知/伪造归属字段、重放、超限或截断控制帧导致整个通道失败并取消 Job;无权操作和路径错误返回有界错误。普通命令输出可继续流式传递,不使用控制帧上限截断普通长行。
每个操作保存 pending/running 和最终 succeeded/denied/failed/cancelled 状态。最终记录在回复之前写入并刷新;只记录身份和元数据,不保存文件内容或活跃快照 token。回复丢失不会把已经成功的读取改成未执行;在运行中中断而没有最终状态的记录查询为 unknown。
```powershell
npm run sandbox:maintain -- broker-operation RUN_UUID REQUEST_UUID --state-root 'C:\absolute\userData\sandbox'
```
真实 preload 提供 `window.ycode.app.sandboxBrokerOperation(runId, requestId)`;仅允许自有顶层应用页面,校验 UUID、记录类型以及 session/run/tool-call/策略归属。不能由另一个 Job 的 requestId 替换目标。查询接口不接受任意路径或 stateRoot。
## 验证与未完成项
测试使用隔离工作区、状态目录和 Electron userData,不改用户生产策略/运行时,不重启用户当前窗口。
| 验证 | 已确认结果 | 报告 |
| --- | --- | --- |
| 实际读取桥 | 21/21;同步/异步、二进制分块、根替换/junction 竞争、后代绕过拒绝、缓存一致性、归属/重放和策略撤销 | `.tmp/sandbox-read-bridge-fcJNOa/report.json` |
| 协议故障 | 15/15;长普通输出/分帧、快照/并发上限、伪造/重放/超限/截断、取消、丢失回复和 unknown 结果 | `.tmp/sandbox-read-protocol-faEL5f/report.json` |
| 运行时兼容 | 20/20;Node 同步进程和 Python TEMP/0o700、真实子进程、原生拒绝及三个资源被篡改时 fail closed | `.tmp/sandbox-process-9EyXTm/report.json` |
| 既有基础 | 原生 52/52、策略 38/38、旧 stdio 10/10 | `.tmp/sandbox-regression-GJmnRy/report.json`、`.tmp/sandbox-policy-0ZW6xX/report.json`、`.tmp/sandbox-stdio-AsODUy/report.json` |
| 工具链/依赖 | 24/24;Node/npm、Python/pip 和工作区绑定的只读依赖层仍可用 | `.tmp/sandbox-toolchain-IvxSbF/report.json` |
| 生产整应用 | 26/26;真实 main/preload/renderer、OMP RPC、localhost 模型;显式同步进程预加载、审批写回、读取桥及其持久查询 | `.tmp/sandbox-app-1LP378/report.json` |
最终 8 组共 206 项通过,完整生产构建/类型检查通过。原生资源更新为 0.2.0,协议仍为 1;三个适配资源均纳入安装状态校验,缺失或摘要不符时不显示 installed。源码/组件身份与报告摘要另存 `.tmp/sandbox-read-integration-validation.json`。文件格式检查已按本仓库 Windows CRLF 配置验证;并行模块未做全库换行重写。
尚未完成:宿主写入的原生事务代理、描述符/追加等完整文件语义、Python/Shell 桥、逐后代认证、标准用户与其他 Windows/文件系统矩阵、安全中心页面及原版 1:1。目录/文件元数据写回、磁盘硬限额、长期工作区、Git、受控出站、MCP/ConPTY 等继续按总计划推进。
## 原生 API 依据
[CreateFileW](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew) 规定共享模式及打开 reparse 对象的行为;[GetFinalPathNameByHandleW](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew) 提供句柄规范路径和 volume GUID 形式;[FILE_ID_BOTH_DIR_INFO](https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_both_dir_info) 定义句柄目录枚举记录。这些文档用于解释具体执行点;当前支持程度由自有实际攻击/故障测试记录限定,不由 API 名称推定完整文件代理安全。