fix(web): address the review of the workspace-file route
Isolation is restored on the premise the review corrected: a workspace file need not be agent-authored — a read row makes every file in a cloned repository openable — and a same-origin active document was measured driving /api/settings.describe to a 200 with full data. Script-capable documents go back into an opaque origin; the preview's lost localStorage is the known cost, and a separate serving origin is the way to retire it. - confine(): a workspace rooted at a filesystem root has a realpath already ending in the separator, and the doubled prefix 403'd every child. - turnDeliverables(): reset on the turn boundary, not only at a closing assistant, so an interrupted turn cannot spill into the next turn's row; and recognize a mutation by render intent (diff card, or generic with kind 'edit') so str_replace_editor's insert counts. - 405 answers name the methods it allows. - The e2e now cold-seeds a recorded WRITE turn, so the assembled application covers the Produced row, its chip's served URL, and the isolation header. - Agent Note matched to what shipped (the row is in this PR, not deferred); ui-conversation README documents the new destination and the row; the fixture lane's dead-tab quirk and the cold-path listing cost are recorded.
This commit is contained in:
@@ -12,10 +12,12 @@ node 半侧在桥接前守卫 `/api` 下的每个请求(`src/api-request-trust
|
||||
|
||||
node 半侧还会在 `/f/<sessionId>/<segments…>` 下逐个提供某个 Session 工作区里的文件,让产出的交付物能从报告它的那个页面直接抵达——`http` 页面无法跟随 `file://` 链接,而不在 Host 机器上的浏览器本来也没有那条路径。段落走 URL 而非查询参数,是为了让所服务文档的相对引用能解析到它的同级文件。请求指名一个 Session,由网关指名该 Session 的目录(`ApiProxy.workspaceRootOf`,它从活跃 agent 的 header 或持久化存储作答,绝不会为了提供一个文件而恢复 agent);本包读取这个权威来源而不去够核心服务,因为持有它们的 host 侧 Context 声明会把它们盖到浏览器运行时自己的声明之上。URL 形状本身与其余浏览器可导入的契约面放在一起,位于 [`@deepseek-ai/dsh-host-apiproxy/api`](../../host/apiproxy/README.md),因此构造 URL 的浏览器半侧与解析 URL 的这一半共享同一个编码决定。cwd 与解析出的目标在比较前都要过 `realpath`,因此工作区内指向工作区外的符号链接会因其目标而被拒绝,而不是因其名字;穿越写法拒得更早,在解析期、任何文件系统调用之前。读取是流式的(没有请求会把文件缓冲起来),只应答 `GET`/`HEAD`,并带上 `nosniff` 与 `no-store`。所服务的内容类型表之外的扩展名一律按 `text/plain` 定型而非作为下载给出,因为工作区读取本就是一个“让我看看这个文件”的请求。
|
||||
|
||||
所服务的文档不带任何隔离头,与 `/api` 同源。这是一个决定,不是遗漏:这些文件的唯一作者,正是那个已经握着本用户 shell 与文件系统的 agent,因此 `Content-Security-Policy: sandbox` 只会立在一条它早已越过的信任边界之后,代价却是每个预览都失去 `localStorage` 与 cookie——一个会记住主题的生成页面在它之下就是坏的。把 `dsh web` 服务到回环之外的部署,应当把工作区内容按可信处理,而这一点对其 agent 所做的其他一切本来就已成立。当工作区内容不再属于观看者本人时,隔离预览才成为一个真问题;那时的答案是一个独立的源,而不是一个头。这条前缀由同一道信任 fence 把守,因此配置了 `trustedHosts` 的部署提供工作区文件的范围,与它提供普通读取的范围完全一致。
|
||||
能执行脚本的文档——`.html`、`.htm`、`.xhtml`、`.svg`——还会额外带上 `Content-Security-Policy: sandbox allow-scripts allow-popups allow-modals allow-forms`,让它们运行在不透明源中。工作区文件未必由 agent 撰写:一条 read 行就能让 clone 下来的仓库里任何文件变得可打开,因此与 `/api` 同源提供的活动文档,其脚本会带着浏览器信任 fence 通行到每一个方法,包括那些正因会改动设置与凭据而被钉在回环的方法。代价由预览承担——其中无法使用 `localStorage`、cookie 与同源 `fetch`,因此一个会记住主题的生成页面在预览里记不住——而 `host.openPath` 仍是在 Host 机器上以完整能力打开同一文件的方式。要在不重新打开这个洞的前提下取回那些能力,需要的是一个独立的源,而不是一个更弱的头。这条前缀由同一道信任 fence 把守,因此配置了 `trustedHosts` 的部署提供工作区文件的范围,与它提供普通读取的范围完全一致。
|
||||
|
||||
## 无密钥 fixture
|
||||
|
||||
fixture 载体没有 `/f` 路由,而 `IWorkspaces.fileUrl` 无论载体为何都在浏览器侧推导 URL,因此在 `fixture=` 下点击文件路径行会打开一个 404 的标签页,而此处从前是 Host 打开器的静默空操作。今天的 fixture 页面并不含文件行;若某个 fixture 场景要加上它们,应当把这段推导打桩,而不是教这个内存载体去提供字节。
|
||||
|
||||
任何 `fixture` 查询参数都会选择内存载体。`fixture=empty` 启动时不含 Workspace 或 Session;`fixturePrompt=reject` 在接受前拒绝提示词;`fixtureAttach=fail` 发布 Session 但拒绝将其附加到 Workspace;`fixtureSessionCreate=drop-response` 在丢弃创建响应前发布 Session 并为其发出帧;`fixtureFrames=workspace-first` 则反转默认的 Session 优先创建帧顺序。按名称/路径创建 Workspace 以及由调用方预先分配 SessionId,均具有足够的确定性,组装后的 Web 测试可以据此协调列表与帧的到达。fixture 内容搜索会保留面向生产环境的 `unicode61` 式大小写、变音符号和 token/短语行为,并返回以匹配位置为中心、最多包含 120 个 Unicode 码点的 snippet。
|
||||
|
||||
## 模型体验
|
||||
|
||||
Reference in New Issue
Block a user