# 安全策略 ## 支持版本 | 版本 | 支持状态 | | --- | --- | | master | ✅ | ## 报告漏洞 **请勿通过公开 Issue 报告安全漏洞。** 请通过 [GitHub Security Advisories](https://github.com/leanderli/dsh-files-git/security/advisories/new) 提交,或开启一个仅维护者可见的私有安全公告。我们会在收到后尽快回复。 ## 安全模型(本插件的信任边界) 本插件执行 `git` 命令与文件读写的位置是一个**独立服务进程** (`lib/server/server.js`,由 host 半区按需拉起);DSH 内的 host 半区只做 生命周期管理与回环代理,不直接执行 git。安全边界如下: ### 1. RPC 通道:仅回环可信 - `/git-api/*` 端点经 DSH 连接服务的请求拒绝器(`requestRejection`: Host/Origin 信任 + 浏览器 Cookie 认证)把关,复用 DSH `/api` 通道的 浏览器信任围栏; - 仅回环来源(`127.0.0.1` / `localhost` / `[::1]`)可调用;从局域网 / 公网 地址访问 WebUI 时,所有 `/git-api` 操作会被拒绝; - 通道走 HTTP POST + 标准 client-request 信封,不额外向浏览器开放端口; - **内部 sidecar 通道**:面板可经 `/git-api/service-info`(回环围栏内)取得 服务端口与 token 后**直连** `127.0.0.1:<随机端口>`,也可走 host 代理;两条 路径的每个请求都必须携带随机 Bearer token(401 拒绝无 token 请求)。token 只存在于用户主目录的运行时文件 `~/.dsh-files-git/dsh-files-git-service-<指纹>.json` (目录与文件均仅当前用户可读;文件名按配置指纹命名,不同配置的 DSH 实例 各有独立服务)。服务端 CORS 仅对回环来源精确回显——局域网 来源既拿不到 CORS 许可、也拿不到 token(`/git-api` 回环围栏先行拒绝), 双重 fail-closed; - **`/vendor` 静态资源(免 token)**:sidecar 以 `GET /vendor/monaco/**` 托管 Monaco 编辑器静态文件——AMD loader 与 blob worker 无法携带 Authorization 头,故该路由免 token;围栏 = 仅绑回环 + 路径解析限定在 `vendor/` 目录内(穿越 403 / 可规范化穿越在 token 门 fail-closed)+ 并发上限与流式响应(无整文件缓冲)。资源为只读公开编辑器产物,不含 任何用户数据; - **SSE 推送(/events)**:与 RPC 同一 token 围栏,只读推送 `git status` 的结构化结果,不执行任何变更类操作;并发流上限 4,流断开即停止对应 工作区的文件监视。 ### 2. 文件访问:工作区包含校验 - `list` / `read` / `search` 与 `write` 的 `path`(相对路径)分支限定在当前 会话工作区根目录内:`path.resolve` + `fs.realpath` **双重包含校验**, 拒绝 `..` 穿越、绝对路径与符号链接逃逸; - `delete`:仅接受工作区相对路径(**无 `abs` 分支**——杂散绝对路径必然过不了 包含校验),同样的双重包含校验;显式拒绝 `.git` 目录及其内容、拒绝工作区 根目录本身;目录递归删除(`fs.rm recursive`),且仅在前端二次确认后由 浏览器发起; - **显式信任的例外**: - `readPath`:按**绝对路径**只读(512KB 上限),不受工作区根约束; - `write` 的 `abs` 分支:按绝对路径写回(512KB 上限); - `stat`:按**绝对路径**探测类型(仅返回 `dir` / `file` / `other`,永不 返回内容)——用于「产物链接点击是文件还是目录」的分流判断与上传覆盖 探测;信息暴露上限 = 本机路径的存在性与类型,且同样需要 token。 前两个端点只为「面板预览 / 编辑过的文件」服务:浏览器端只会传回 host 刚刚读取过的真实文件路径(面板不会构造任意路径)。它们是「产物链接预览」 功能(可读取工作区之外的产物文件)的必要妥协,默认能力边界 = 面板里出现过的路径。 ### 3. 命令执行:无 shell 注入面 - 所有 git 命令经 `spawn(gitPath, ["-C", repo, ...args])` 的 **argv 数组** 执行,永不拼接 shell 字符串;提交信息、路径、分支名无法注入命令语法; - `gitPath` 默认从 `PATH` 解析(跳过 `.git-ai` shim 目录),可经 `cordis.patch.yml` 配置为绝对路径; - 子进程设置 `GIT_TERMINAL_PROMPT=0`(凭据提示快速失败而非挂起)与 `LC_ALL=C`(输出可解析);单命令输出上限 512KB、超时 (状态类 15s / 变更类 180s)后**进程树终止**(taskkill /T /F,防止 git 辅助进程残留锁文件);git 进程启动即崩溃(Windows 0xC0000142)时 自动重试一次。 ### 4. 输出与大小限制 - 单条命令捕获输出 ≤ 512KB;文本读取 ≤ 512KB(超出截断并标记); - diff 渲染带行数上限与词级高亮配对数上限,超大差异降级为行级着色。 ### 5. 前端 - 面板经 portal 辅助渲染于受控 dialog 层,焦点陷阱 + 滚动锁定, 弹层不穿透; - Monaco 编辑器(编辑与 diff 共用)由 sidecar 进程本地托管 (`/vendor/monaco`,见 §1 的免 token 路由),**不依赖任何第三方 CDN**—— 无供应链注入面,离线可用;Monaco 不可用时自动降级为纯文本渲染并保留 系统应用打开兜底; - **HTML 预览沙箱**:HTML 文件在 `sandbox="allow-scripts allow-modals"` 的 iframe 中渲染——刻意**不带 `allow-same-origin`**,文档运行在不透明 origin: 页面脚本可执行,但无法触及面板、DSH 应用及其任何存储;预览前在文档首个 脚本前注入一段内联 shim,用内存版 localStorage/sessionStorage 顶替被禁的 原生 API(只作用于预览文档自身,不触达宿主);表单与弹窗保持禁用。iframe 内容为构建期/运行期读取的工作区文件本身,与 `readPath`/`readBlob` 的信任 模型一致。 ## 已知考量 - **回环外的 WebUI 暴露**:默认绑定(127.0.0.1)下,`/git-api` 对非回环 来源 fail-closed。DSH 原生局域网模式(`dsh web --host 0.0.0.0`,控制台 打印带 token 的 LAN URL,首次访问换发绑定 authority 的会话 Cookie, 默认 30 天)下,`/git-api` 与 `/api` 共用部署级围栏(Host/Origin 信任 + 会话鉴权);本插件不放宽自身围栏,也无需放宽。此时面板在非回环来源 自动降级为纯代理模式(不尝试直连、无 SSE 推送),sidecar 仍只绑服务器 本机回环、随机端口不暴露。注意:启动 token 与会话 Cookie 授权的是 整个 DSH harness(所有会话与插件,不只是 git 面板),LAN URL 应按密钥 保管,勿入日志与聊天记录; - **工作区即边界**:插件对工作区内的**任意** git 操作有完整权限(包括 `reset --hard`、force push 等),危险操作有二次确认,但确认后即真实执行; 文件 / 目录删除(目录递归)为**不可逆**磁盘操作,同样二次确认后真实执行; - **运行时文件**:`~/.dsh-files-git/dsh-files-git-service-<指纹>.json` 记录 服务端口与 token,供同用户同机器、同配置的 host 半区复用服务(指纹=spawn 配置摘要,不同配置的实例各得一个文件与一个服务,互不杀伐)。目录与文件 均以当前用户权限创建(目录 0700 / 文件 0600,其他用户不可读)——不再落入 多人共享的系统临时目录。删除它安全——下次请求会重新拉起服务并换发新 token。服务空闲 30 分钟自退出;导出缓存与临时上传清理同在该目录下进行。