资讯详情

OpenChamber /api/fs 完全指南:Web 服务器如何安全管控工作区文件、媒体预览与后台命令

📅 2026/11/3 19:06:10 | 华诺云谱 👁 阅读
OpenChamber /api/fs 完全指南:Web 服务器如何安全管控工作区文件、媒体预览与后台命令
OpenChamber /api/fs 完全指南Web 服务器如何安全管控工作区文件、媒体预览与后台命令【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址: https://gitcode.com/gh_mirrors/op/openchamberOpenChamber基于 OpenCode AI agent 的 Agentic 开发环境的桌面端、Web 端与移动端所有文件读写、目录列举、媒体预览和命令执行最终都收敛到同一个 Web 服务器运行时暴露的一组/api/fs/*HTTP 端点上。这套能力由 OpenChamber Web 服务器 FS 模块位于packages/web/server/lib/fs/独立承载它拥有自己的工作区边界策略、错误映射、超时行为与任务队列而不把这些逻辑散落在组合根里。这篇指南带你从接口面出发逐层拆解它的边界校验、Range 流式预览、后台执行任务队列、git 子进程纪律与可配置项读完可以直接上手调试或扩展新端点。模块定位为什么文件系统策略必须收进服务端一个模块想象一下另一种架构组合根packages/web/index.js里混杂着目录列举处理器、上传限流、exec 超时逻辑。每加一个端点边界校验就会被复制一遍策略开始漂移。OpenChamber 的选择是反过来的——把整套文件系统行为封装成内聚模块组合根只负责供给模块负责裁决。模块由几个协作文件构成文件职责routes.js注册全部/api/fs/*端点并持有运行时私有状态exec 任务队列execJobs、git 读缓存gitReadCache、工作区外文件授权grantssearch.js模糊文件系统搜索运行时createFsSearchRuntime被非 FS 路由如项目图标发现复用byte-range.js把Range: bytes…请求头约简为唯一字节跨度驱动 206 响应DOCUMENTATION.md模块自身的行为契约与贡献者约定装配关系值得注意index.js只提供组合期依赖——平台原语与回调resolveProjectDirectory、normalizeDirectoryPath、buildAugmentedPath等不再持有任何 FS 路由处理器或 exec 任务状态真正把os、path、fsPromises、spawn、crypto以及openchamberUserConfigRoot、managedChatsRoot注入registerFsRoutes的是 feature-routes-runtime.js。文档把这条边界写成硬性约定工作区根校验、错误映射、exec 超时行为属于模块内部资产禁止外泄到组合根。接口面总览16 个端点覆盖目录、文件、媒体与执行registerFsRoutes(app, dependencies)注册的全部端点如下按能力归组分组端点用途元信息GET /api/fs/home返回{ home, chatsRoot, canonicalChatsRoot, canonicalLegacyChatsRoot }目录POST /api/fs/mkdir在工作区内递归创建目录目录GET /api/fs/list列目录可带respectGitignoretrue目录GET /api/fs/directory-stat单目录 stat不列内容、不解析项目拓扑目录GET /api/fs/git-dirs浅层发现嵌套 git 仓库供 Git 标签页选仓库文件GET /api/fs/read按 UTF-8 读文本返回text/plain文件GET /api/fs/raw读原始字节支持Range与下载文件GET /api/fs/stat单文件 stat{ path, isFile, size, mtimeMs }文件POST /api/fs/write原子写文本文件文件POST /api/fs/upload流式上传单个二进制文件文件POST /api/fs/delete递归删除文件或目录文件POST /api/fs/rename同一工作区根内移动/重命名文件POST /api/fs/reveal在系统文件管理器中定位路径媒体GET /api/fs/serve/:path(*)无令牌的静态预览HTML、图片、PDF 等执行POST /api/fs/exec在指定 cwd 顺序执行一组 shell 命令执行GET /api/fs/exec/:jobId轮询命令执行任务状态除端点外模块还导出了mintOutsideFileGrant(targetPath, options)为工作区外的单个文件铸造临时授权令牌作用域默认stat/read/rawTTL 10 分钟目标必须是文件且先经fs.realpath规范化见 routes.js。安全边界三层工作区校验与符号链接的陷阱所有写类路由mkdir、write、upload、delete、rename、exec与绝大多数读路由动手前都要先过resolveWorkspacePathFromContext这道闸门。它按三个来源依次放行活动工作区——把目标path.resolve后要求落在resolveProjectDirectory(req)返回的活动项目目录内部Managed roots——OpenChamber 配置根与托管聊天根managedChatsRoot上游即OPENCHAMBER_CHATS_DIR默认config root/chats。聊天 worktree 完全可能合法地躺在任何项目工作区之外所以这两个受管根始终视为合法目标Worktree 回退——通过../git/index.js的getWorktrees枚举活动仓库的全部 worktree把每个 worktree 根也纳入白名单。真正有意思的是符号链接。活动项目目录是用fs.realpath校验过的规范化路径一旦项目根本身是个符号链接客户端发来的路径仍停留在用户可见的原始目录上两者就对不上了。模块的解法是先用客户端实际请求的原始目录resolveProjectDirectory返回的requestedDirectory做一次词法比对再解析符号链接而写路由与 exec 路由还会对规范化基座追加一次规范包含检查canonical containment check堵死借符号链接逃逸的路线。配套细节还有几条每个 managed root 都会经canonicalDirectoryPath处理——把不存在的路径解析到最近存在的祖先再拼接缺失片段。只解析绝不创建目录。路径比较在 POSIX 上保持大小写敏感Linux 下名为chats和Chats的两个目录永远不会因字符串折叠而互为别名。读类路由read、raw、stat在allowOutsideWorkspacetrue时直接放行到工作区外仍受服务端认证与操作系统权限约束不需要单独的文件授权遗留的 grant 参数被忽略工作区内的读取则在检查请求路径之后再解析符号链接。数据流之一读文件、原始字节与 206 Range 流式预览三个读端点各有分工其中/api/fs/read藏着一个精巧的竞态处理。空读重试文件可能在你stat和readFile之间被另一个进程改写。如果stat显示有内容而readFile却返回空串说明并发写入者正处在O_TRUNC窗口——文件已截断、新内容还没写回。此时按 50/100/150ms 间隔最多重试 3 次避免客户端拿到半成品。optionaltrue时文件不存在返回空串而非 404。Range 与 206/api/fs/raw由 byte-range.js 提供区间语义——单个Range: bytes…头被约简为唯一字节跨度resolveByteRange给出range/whole/unsatisfiable三种结果。为什么这么在意因为媒体元素首次请求bytes0-、每次 seek 都发bytesoffset-而Chromium 与 WebKit 在没有 206 响应时拒绝 seekSafari 甚至拒绝播放视图器里的音视频播放器完全吃这一套。设计上还做了两处克制多段 Range 直接按整文件处理没有媒体消费者会发多段请求不值得为预览器背负 multipart 响应体unsatisfiable区间如bytes-0、start ≥ size、end start返回416加Content-Range: bytes */size。命中的区间以206Content-RangecreateReadStream({ start, end })从磁盘流式输出句柄随流关闭成功失败路径一致整文件请求则整体读入内存发送。响应固定带Accept-Ranges: bytes与no-storedownloadtrue时按 RFC 5987 同时输出 ASCIIfilename与 UTF-8filename*UTF-8...两种 Content-Disposition。无令牌预览/api/fs/serve用于在页面内预览 HTML/图片/PDF。它显式拒绝allowOutsideWorkspacetrue且单文件上限MAX_SERVE_BYTES 100 MiB超出 413。它的 MIME 表FILE_MIME_MAP与 raw 路由共享覆盖 HTML/CSS/JS、图片、PDF、音频mp3/m4a/aac/wav/flac/ogg/opus/weba、视频mp4/m4v/webm/mov/ogv/mkv、字体woff2/woff/ttf/otf/eot、CSV/TSV.mmd按text/plain处理。这里有个容易被忽略的子资源鉴权约定serve 出来的页面其相对引用的图片、样式、脚本在加载时不带令牌。ui-auth.js 会从Referer头接受页面自身的oc_url_token但仅当两个 URL 都位于/api/fs/serve/之下。桌面端在 loopback API 源上没有会话 Cookie缺了这条机制HTML 预览里的图片就会成片加载失败。数据流之二写操作——原子写入、流式上传与不可逃逸的父目录/api/fs/write临时文件 rename。写入前有两道防护先realpath解析目标ENOENT时保留原路径再对规范化基座做isPathWithinRoot包含检查失败 403。幂等性也有保障——目标已存在且内容与请求完全相同时直接返回成功。真正的落盘走临时文件 rename写成path.tmp-pid-时间戳-随机串后原子 rename 到目标。为什么不直接writeFile因为直接写会暴露在O_TRUNC窗口里并发读者可能看到一个空文件临时文件写入失败则立即清理。/api/fs/upload流式、限额、无覆盖提交。这是契约最严的端点请求体必须是application/octet-stream否则 415目标由path查询参数指定overwritetrue显式声明覆盖Content-Length超限直接 413否则经streamUploadBody逐块写入同目录临时文件累计字节超限同样 413 并清理目标已存在是目录则 400未声明overwrite则 409reason: already-exists声明覆盖则 rename 临时文件到位新文件用同目录硬链接fs.link原子提交随后删除临时文件。链接天然不会替换检查之后新出现的同名目标而临时文件已完全落盘——读者永远看不到半截上传父目录在写入前就解析requestedParent被realpath规范化并做包含检查上传无法借工作区符号链接逃逸到外部。默认上限 100 MiB可用OPENCHAMBER_FS_UPLOAD_MAX_BYTES覆盖routes.test.js 里就有把上限临时改为 5 字节并断言 413 的用例。delete 与 rename删除是fsPromises.rm(path, { recursive: true, force: true })重命名要求oldPath与newPath解析出的base严格相等Source and destination must share the same workspace root从机制上禁止跨工作区根搬文件。数据流之三后台执行——/api/fs/exec任务队列与 git 读缓存POST /api/fs/exec接收{ commands: string[], cwd, background }。注意background字段当前被显式拒绝Background command execution is not allowed任务总是前台顺序执行。执行流程cwd 必须通过工作区解析越界 403且确实是个目录shell 取process.env.SHELLWindows 回退cmd.exeflag/c其余平台回退/bin/shflag-c任务以crypto.randomUUID()生成jobId入队到模块私有的execJobsMap状态机queued → running → done每条命令经runCommandInDirectory执行stdout/stderr 收集后 trimexitCode 0且未超时才算成功GET /api/fs/exec/:jobId轮询状态与逐条results未命中 404。生命周期由两把剪刀控制任务 TTLEXEC_JOB_TTL_MS 30 分钟。pruneExecJobs在创建任务与轮询时都顺手清理超龄任务防止长驻服务端内存无限膨胀单命令超时OPENCHAMBER_FS_EXEC_TIMEOUT_MS默认 5 分钟超时SIGKILL子进程结果里带上Command timed out after msms。exec 层内还嵌了一个gitReadCache规则非常克制只有白名单内的确定性、无副作用git plumbing 路径查询git rev-parse --absolute-git-dir、--git-common-dir、--show-toplevel的组合才可进缓存其他任何命令包括非 git 命令一律正常执行、绝不写缓存。缓存本身是条数 字节双约束上限 500 条且总量不超过 1 MiBLRU 逐出TTL 默认 30 秒OPENCHAMBER_GIT_READ_CACHE_TTL_MS设 0 关闭同 key 的在途请求经inFlightGitReadCache合并去重且只缓存成功结果——失败可能只是暂时的。它的实战意义页面刷新后客户端对每个项目触发的那批相同rev-parse查询可以被安全地吸进缓存。运维陷阱git check-ignore的 stderr 纪律这是整个模块里最运维向的一条军规文档与代码反复强调search 与 directory listing 都会在 spawn 时丢弃git check-ignore的 stderrstdio: [ignore, pipe, ignore]只消费 stdout 的忽略匹配结果。永远不要创建一条没人读的 stderr 管道。原理很朴素子进程的 stderr 是操作系统管理的管道缓冲区有限。Git 的诊断输出一旦填满这条无人消费的管道写操作就会阻塞子进程永久挂起而反复搜索会不断累积这样的活进程。git-process.test.js 用真实 OS 管道把这个坑钉死了它用一个每子进程向 stderr 灌 2 MiB 诊断32 × 64 KiB的noisyGit桩替换 git然后对search与list两条路径各发起12 个并发检查断言全部完成、忽略过滤仍生效visible.txt可见、ignored.txt被剔除、12 个子进程全部以退出码 0 被回收。任何改动若引入未读的 stderr 管道都会在该测试的 5 秒看门狗下超时失败。超时兜底同样存在git check-ignore子进程受OPENCHAMBER_GIT_CHECK_IGNORE_TIMEOUT_MS默认 2500ms保护超时直接SIGKILL并按无忽略结果继续。浅层仓库发现与模糊搜索git-dirs与search.js两个受限遍历器GET /api/fs/git-dirs?path...服务于 Git 标签页的选择仓库下拉框。findGitDirectories是受限遍历GIT_DIRS_MAX_DEPTH 3、GIT_DIRS_MAX_DIRS 100跳过node_modules、dist、build、.venv、target、.next等垃圾目录且符号链接目录绝不深入防遍历逃逸与环。一个目录出现.git目录、worktree 指针文件、符号链接都算即构成仓库边界并停止下探嵌套仓库不会重复上报根本身是仓库时不上报根。不可读的非根子树静默跳过但根自身不可读会按共享错误处理映射为 403/404/500。search.js 的createFsSearchRuntime({ fsPromises, path, spawn, resolveGitBinaryForSpawn })返回{ searchFilesystemFiles(rootPath, options) }被非 FS 路由如项目图标发现复用。其设计要点并发受限的 BFSFILE_SEARCH_MAX_CONCURRENCY 5队列按批并行读目录排除目录node_modules、.git、dist、build、.next、.turbo、.cache、coverage、tmp、logs大小写不敏感includeHiddenfalse默认时跳过点号开头的条目gitignore 过滤respectGitignore默认开启每目录批量git check-ignore同样以stdio: [ignore, pipe, ignore]丢弃 stderr模糊匹配评分子串命中给前缀 20 分 / 分隔符前 15 分加成再扣偏移与长度惩罚非子串命中按字符顺序、相邻度、分隔符前缀与路径长度综合计分低于阈值返回null剔除同分先按相对路径长度、再按字典序稳定排序上限逻辑空查询matchAll收集到limit即止有查询时先收集max(limit*3, 200)的候选池再排序取前limit保证取到的是全局最优而非批次截断的结果。另外/api/fs/list还有一个易漏的约定realpath 只用于读取目录内容响应里的path与每个 entry 的path必须用path.join(requestedPath, name)构造停留在调用方的请求路径空间里。否则通过工作区符号链接访问的目录会在文件树展开时因路径落在工作区根之外而被 UI 拒绝。顺带一提list对.opencode/plans结尾且尚不存在的路径如~/.opencode/plans返回{ path, entries: [] }而非 404保证计划目录在首次创建前也能被 UI 正常展开。错误契约四个稳定的 reason 标记模块把错误映射收敛成稳定契约客户端据此区分可补救与不可补救reason标记HTTP触发场景os-permission403EACCES/EPERM文件系统层权限拒绝。原生文件夹选择器可以补救所以这是稳定的响应标记already-exists409上传目标已存在且未声明overwritenot-found404目录/文件不存在not-directory400目标是文件或ENOTDIR一条关键原则工作区边界策略拒绝如 Path is outside of active workspace不得使用os-permission标记——它不是 OS 权限问题原生文件夹选择器根本无从补救。/api/fs/directory-stat与/api/fs/list遵循同一鉴权目录发现策略允许查询活动工作区之外的目标但权限失败与路径不存在始终保持区分。VS Code 对directory-stat显式返回 501共享客户端因此把它的探针结果视为未知。配置与扩展环境变量速查表与贡献者清单全部可调参数都来自服务端进程环境环境变量默认值作用OPENCHAMBER_FS_UPLOAD_MAX_BYTES100 MiB上传体积上限非正数回退默认OPENCHAMBER_FS_EXEC_TIMEOUT_MS5 分钟单条 exec 命令超时超时SIGKILLOPENCHAMBER_GIT_READ_CACHE_TTL_MS30 秒git 读缓存 TTL0关闭缓存OPENCHAMBER_GIT_CHECK_IGNORE_TIMEOUT_MS2500 msgit check-ignore子进程超时超时SIGKILL按无忽略处理OPENCHAMBER_CHATS_DIRconfig root/chats托管聊天根managedChatsRoot可整体搬迁如果你要为/api/fs/*新增端点模块内既有约定就是一份验收清单路由加在 routes.js 并同步扩展 DOCUMENTATION.md工作区校验一律走resolveWorkspacePathFromContext读类路由走resolveReadPathFromContext不要自己发明包含判断OS 权限错误映射到reason: os-permission策略拒绝绝不复用该标记任何git check-ignore子进程必须丢弃 stderr、只消费 stdout并接受超时保护——git-process.test.js的并发大诊断管道测试会替你验证/api/fs/list的响应路径必须停留在请求方路径空间上传类端点必须先解析目标父目录并做规范包含检查。遵循这些约定新端点就能无缝继承整个模块的鉴权、错误映射与资源回收纪律——这正是把它从组合根里剥离出来的全部意义。【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址: https://gitcode.com/gh_mirrors/op/openchamber创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑