Files
qiming/qimingclaw/docs/sandbox-whitelist-blacklist-plan.md

490 lines
23 KiB
Markdown
Raw Permalink 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-04-09
> 上游审查依据:`sandbox-plan.md`两轮审查12 条问题已确认)
---
## 1. 范围边界
### 1.1 In Scope
- 平台macOS / Linux / Windows
- 后端:`macos-seatbelt` / `linux-bwrap` / `windows-sandbox` / `docker`(当前未实现,显式标注 `unsupported`
- 沙箱模式(`mode``strict` / `compat` / `permissive`(全平台通用,含 Windows
- Windows 子模式(`windowsMode`,独立维度):`read-only` / `workspace-write`,与 `mode` 正交组合
- 双层覆盖:
1. **沙箱层**:文件系统、网络、进程执行、设备访问的 allow/block/conditional 行为
2. **命令权限层**`PermissionManager` 的 safeCommands 白名单与危险命令黑名单行为
### 1.2 Out of Scope
- GUI Agent 沙箱策略与测试
- 非沙箱业务功能
---
## 2. 核心设计决策
### 决策 1矩阵是"规范文档"normative而非从实现反向生成
若矩阵从实现代码自动提取,则实现的 bug 会被误记为"正确行为",规范价值丧失。
**确定方向**:矩阵由人工审查后手动维护 → 测试验证实现是否符合矩阵 → CI 检查矩阵与生成文档是否同步。
```
手工维护 sandbox-matrix.spec.json ← 专家审查 + PR review
测试驱动(从 spec 生成表驱动用例)
验证实现是否符合矩阵
CI: spec → 生成 Markdowngit diff 检查一致性
```
生成脚本(`crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js`)的职责是:`spec.json → .md`,方向单向,不反向提取。
### 决策 2两层矩阵严格分离
| 层 | Spec 文件 | 内容 |
|---|---|---|
| 沙箱层 | `docs/sandbox/sandbox-matrix.spec.json` | 平台/后端/模式下各操作的 verdict |
| 命令权限层 | `docs/sandbox/permission-matrix.spec.json` | safeCommands 白名单 + 危险命令黑名单行为 |
两层不混入同一文件,避免审查与测试范围混淆。
---
## 3. 产物定义
### 3.1 规范文件(手工维护,单一真源)
**`docs/sandbox/sandbox-matrix.spec.json`** — 沙箱层:
```json
{
"cases": [
{
"id": "MAC-SEATBELT-COMPAT-workspace_write",
"platform": "darwin",
"backend": "macos-seatbelt",
"mode": "compat",
"operationId": "workspace_write",
"verdict": "allow",
"evidence": "SandboxInvoker.ts buildSeatbelt: writablePaths → allow file-write*"
},
{
"id": "WIN-SANDBOX-COMPAT-WW-network_external",
"platform": "win32",
"backend": "windows-sandbox",
"mode": "compat",
"windowsMode": "workspace-write",
"operationId": "network_external",
"verdict": "conditional",
"condition": "best-effort suppression via env vars + denybin stubs (ssh/scp); does not block raw socket connections",
"evidence": "windows-sandbox-helper/src/env.rs apply_no_network_to_env()"
},
{
"id": "ALL-DOCKER-ANY-any",
"platform": "*",
"backend": "docker",
"mode": "*",
"operationId": "*",
"verdict": "unsupported",
"reason": "process-level docker wrapping not implemented; SandboxInvoker returns unwrapped command with warning log"
}
]
}
```
**操作枚举operationId**
| ID | 说明 |
|----|------|
| `workspace_write` | 工作区内写文件 |
| `outside_workspace_write` | 工作区外写文件 |
| `system_path_write` | 系统路径写(`/etc`, `/usr`, `C:\Windows` |
| `file_read` | 任意路径读 |
| `network_external` | 外网 TCP/UDP |
| `network_loopback` | loopback127.0.0.1)访问 |
| `process_exec_whitelist` | 白名单路径可执行文件 |
| `process_exec_arbitrary` | 任意路径可执行文件 |
| `privilege_escalation` | sudo/su/提权命令 |
| `device_write` | `/dev` 设备节点写 |
**Verdict 语义**
| 值 | 含义 | 必填字段 |
|----|------|---------|
| `allow` | 操作被允许 | — |
| `block` | 操作被阻断 | — |
| `conditional` | 取决于前置条件或调用方约束 | `condition` |
| `unsupported` | 该组合当前未实现 | `reason` |
所有条目必须有 `evidence` 字段,引用具体源码位置(如 `SandboxInvoker.ts:L123`)。
**通配符语义**`"*"` 表示"适用于该维度的所有值"(如 `"platform": "*"` 表示全平台)。测试代码需显式处理:`c.platform === process.platform || c.platform === "*"`
**`docs/sandbox/permission-matrix.spec.json`** — 命令权限层:
```json
{
"safeCommands": {
"commands": ["node", "npm", "npx", "git", "ls", "cat", "..."],
"expectedBehavior": "auto-approve for command:execute without user confirmation",
"evidence": "PermissionManager.ts DEFAULT_PERMISSION_POLICY.safeCommands"
},
"dangerousCommands": {
"cases": [
{
"id": "PERM-SUDO",
"input": "sudo rm -rf /",
"matchedPattern": "sudo",
"expectedResult": "allowed: false",
"evidence": "PermissionManager.ts DANGEROUS_COMMANDS + checkDangerousOperation word-boundary regex"
},
{
"id": "PERM-CHMOD-777",
"input": "chmod 777 /etc",
"matchedPattern": "chmod 777",
"expectedResult": "allowed: false"
},
{
"id": "PERM-CHMOD-755-FP",
"input": "chmod 755 file",
"matchedPattern": null,
"expectedResult": "allowed: true",
"note": "word-boundary regression: must NOT match 'chmod 777' pattern"
}
]
},
"sensitivePaths": {
"cases": [
{
"id": "PERM-SSH",
"type": "file:write",
"target": "/home/user/.ssh/id_rsa",
"expectedResult": "allowed: false"
},
{
"id": "PERM-ETC-PASSWD",
"type": "file:write",
"target": "/etc/passwd",
"expectedResult": "allowed: false",
"note": "sensitiveEtcPaths 精确匹配;/etc/hosts 当前不在阻止列表中"
}
]
},
"denyList": {
"cases": [
{
"id": "PERM-SYS-PKG",
"type": "package:install:system",
"target": "curl",
"expectedResult": "allowed: false, no confirmation prompt"
}
]
}
}
```
### 3.2 已知 conditional 条目(需在 spec 中显式标注)
| 组合 | operationId | condition |
|------|-------------|-----------|
| Windows / any / `workspace_write` strict | `outside_workspace_write` | `helper 始终放行 command_cwd调用方必须保证 cwd 在 workspace 内` |
| Windows / `windows-sandbox` / any | `network_external` | `best-effort env/stub 抑制,不是内核级隔离` |
| All / `autoFallback=session` | 全部 | `当前与 startup-only 行为一致预留枚举TBD` |
| macOS seatbelt / `sudo` exec | `process_exec_whitelist` | `/usr/bin/sudo` 在 exec 白名单(`^/usr/bin/`),可被执行;但权限提升被 seatbelt deny结果为 conditional |
| Windows / `sandboxed-bash-mcp` | 全部 | `MCP 路径固定传 --no-write-restricted不使用 WRITE_RESTRICTED token依赖 DACL ACE 控制写权限` |
| Linux / `linux-bwrap` / permissive | `network_external`, `network_loopback` | `permissive 走独立代码分支SandboxInvoker.ts:228-239不执行 --unshare-net即使 networkEnabled=false 也不隔离网络` |
### 3.3 生成产物CI 检查)
- `docs/sandbox/sandbox-matrix.generated.md` — 由 `crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js` 从 spec 生成,提交至仓库
- `sandbox-report.json` — 测试执行结果汇总,上传 CI artifact
**CI 一致性检查**:每次 spec 变更后重新生成 `.md`若生成结果与仓库已提交版本不一致PR 门禁失败。
### 3.4 CI 报告 schema
```json
{
"platform": "darwin|linux|win32",
"backend": "macos-seatbelt|linux-bwrap|windows-sandbox",
"mode": "strict|compat|permissive",
"windowsMode": "read-only|workspace-write|null",
"case_id": "MAC-SEATBELT-COMPAT-workspace_write",
"status": "pass|fail|skip",
"duration_ms": 123,
"exit_code": 0,
"notes": ""
}
```
关键指标:`pass_rate`(按平台/后端/模式分组)、`degrade_to_none_count``env_leak_count``policy_violation_count`
---
## 4. 测试方案
### 4.1 沙箱层核心测试清单
> Windows 列的行为取决于 `mode` × `windowsMode` 组合,下表以 `compat` + `workspace-write`(默认配置)为基准。
| 操作 | macOS seatbelt | Linux bwrap | Windows sandboxcompat + workspace-write |
|------|---------------|-------------|----------------------------------------------|
| workspace 内写 | allow | allow | allow |
| workspace 外写 | block | block | conditionalhelper 放行 `command_cwd`,需调用方约束) |
| 系统路径写/删 | block | block | block |
| 外网访问 | 按 `networkEnabled`true → `(allow network*)`,适用所有模式) | strict/compat`networkEnabled`false → `--unshare-net`**permissive不隔离网络**(走独立分支,`--unshare-net` 不执行) | conditionalbest-effort env/stub非内核级 |
| loopback 访问 | 同外网,受 `networkEnabled` 控制(与 mode 无关) | 同外网permissive 例外:始终可访问) | 需实测 |
| `sudo``/usr/bin/sudo` | **conditional**exec 被放行(路径在白名单 `^/usr/bin/`),但权限提升被 seatbelt deny | block`/usr/bin/` 不在 strict 挂载面 / bwrap 命名空间隔离) | blockPermissionManager 层拦截) |
| `reboot`/`shutdown``/sbin/` | block`/sbin/` 不在 exec 白名单) | block | block |
| workspace 内写(`read-only` 模式时) | N/A | N/A | **block**`ReadOnly` 策略无 `writable_roots` |
### 4.2 命令权限层测试PermissionManager
**新增测试文件**`src/main/services/sandbox/PermissionManager.test.ts`
关键断言:
注意:`checkPermission` 返回 `Promise<PermissionResult>``{ allowed: boolean; reason: string }`),不抛出异常。危险操作返回 `allowed: false`,不进入用户确认队列。
```typescript
// safeCommands 自动放行
it("should auto-approve safeCommands", async () => {
for (const cmd of DEFAULT_PERMISSION_POLICY.safeCommands) {
const r = await pm.checkPermission(sid, "command:execute", cmd);
expect(r.allowed).toBe(true);
}
});
// 危险命令拦截(直接返回 allowed:false不抛出
it("should block dangerous commands", async () => {
const sudo = await pm.checkPermission(sid, "command:execute", "sudo rm -rf /");
expect(sudo.allowed).toBe(false);
const nc = await pm.checkPermission(sid, "command:execute", "nc -l 8080");
expect(nc.allowed).toBe(false);
});
// word-boundary 回归DANGEROUS_COMMANDS 包含 "chmod 777" 字面串
// 应 block "chmod 777 file",不应误杀 "chmod 755 file"
it("should not false-positive on chmod 755", async () => {
const block = await pm.checkPermission(sid, "command:execute", "chmod 777 /etc");
expect(block.allowed).toBe(false);
const safe = await pm.checkPermission(sid, "command:execute", "chmod 755 file");
expect(safe.allowed).toBe(true); // 755 不在危险命令黑名单
});
// 敏感路径拦截
// 注意sensitiveEtcPaths 仅含 /etc/passwd, /etc/shadow, /etc/sudoers, /etc/group
// /etc/hosts 当前 **不在** 阻止列表中(待评估是否需要补入)
it("should block file:write to sensitive paths", async () => {
const passwd = await pm.checkPermission(sid, "file:write", "/etc/passwd");
expect(passwd.allowed).toBe(false);
const shadow = await pm.checkPermission(sid, "file:write", "/etc/shadow");
expect(shadow.allowed).toBe(false);
// .ssh 检测使用 .includes(".ssh"),存在子串误判风险
// "not.sshrc" 也会被拦截(已知限制,需评估是否改为路径段匹配)
const ssh = await pm.checkPermission(sid, "file:write", "/home/user/.ssh/id_rsa");
expect(ssh.allowed).toBe(false);
});
// workspaceOnly 注意PermissionManager 中 workspaceOnly 检查为占位注释,
// 实际路径边界由 WorkspaceManager 在执行时校验。
// 此处仅测试 PermissionManager 不会越权放行 denyList/dangerousOp。
// system packages 在 denyList 中 → checkDangerousOperation 直接返回 allowed:false
it("should deny package:install:system without user confirmation", async () => {
const r = await pm.checkPermission(sid, "package:install:system", "curl");
expect(r.allowed).toBe(false);
// 验证:不应出现在待审批队列中(即未调用 requestPermission
});
```
### 4.3 表驱动集成测试(复用 spec
**新增文件**`tests/sandbox-integration/matrix-driven.test.ts`
```typescript
import spec from "../../docs/sandbox/sandbox-matrix.spec.json";
const runnable = spec.cases.filter(c =>
(c.platform === process.platform || c.platform === "*") &&
c.verdict !== "unsupported"
);
describe.each(runnable)("[$id] $platform/$backend/$mode $operationId", (tc) => {
it(`verdict: ${tc.verdict}`, async () => {
const result = await runSandboxedOperation(tc);
if (tc.verdict === "block") {
expect(result.exitCode).not.toBe(0);
} else if (tc.verdict === "allow") {
expect(result.exitCode).toBe(0);
}
// conditional: 由 tc.condition 描述约束,测试结果写入 notes 字段
});
});
```
**`runSandboxedOperation` 职责**
1. 根据 `tc.backend` 构造 `SandboxInvoker` 实例
2. 根据 `tc.operationId` 映射到具体 shell 操作(如 `workspace_write``echo test > <workspaceDir>/test.txt``network_external``curl -s --max-time 2 https://example.com`
3. 调用 `buildInvocation()` 包装命令,`spawn` 执行,返回 `{ exitCode, stdout, stderr }`
4. 需维护一个 `operationId → shell command` 映射表,确保测试操作可复现
### 4.4 环境变量泄露测试
**新增文件**`tests/sandbox-integration/env-leak.test.ts`
| ID | 路径 | 步骤 | 预期 |
|----|------|------|------|
| ENV-01 | `terminal/create` | 预置 `ANTHROPIC_API_KEY=leak_test`,沙箱内执行 `env` | 输出不含该键 |
| ENV-02 | `sandboxed-bash-mcp` | 同上,经 MCP Bash 路径 | 若出现则标记高危(当前已知风险:仅删 `ELECTRON_RUN_AS_NODE` |
| ENV-03 | helper 直调 | 对比最小 env vs 全量 env | 证明 helper 无内置 safeKeys 过滤(文档与实现一致) |
### 4.5 降级可观测性测试
**文件**`tests/sandbox-integration/fallback-observability.test.ts`
| ID | 场景 | 预期 |
|----|------|------|
| OBS-01 | 模拟 backend 不可用(`autoFallback` 非 manual检查 `app.emit("sandbox:unavailable")` 是否触发 | 事件必须触发,并携带 `reason` |
| OBS-02 | 降级后实际 sandbox type 为 `none`,日志中必须有明确记录 | 日志可查,不静默 |
| OBS-03 | `permissive` 模式启动时,必须有 warning 日志 | warning 可查 |
### 4.6 Windows 集成测试WN 系列,来自 sandbox-plan.md §4.2
| ID | 场景 | 预期 |
|----|------|------|
| WN-01 | `workspace-write + strict`cwd 在 workspace 内写文件 | 成功 |
| WN-02 | `workspace-write + strict`cwd 指向 workspace 外 | 失败(逃逸判定) |
| WN-03 | `read-only` 下写 workspace | 失败 |
| WN-04 | `run + permissive``--no-write-restricted`)创建子进程管道 | 成功 |
| WN-05 | `serve` 模式孙进程启动 | 成功 |
| WN-06 | `network_access=false` 下原生 socket 直连外网 | 应失败(若成功,标记为当前设计限制并写入矩阵 conditional |
| WN-07 | `.git` denyroot 存在 `.git` 时写入 `.git/index.lock` | 失败 |
| WN-08 | world-writable 目录审计超 200 子目录 | 仅扫描前 200日志可见 |
| WN-09 | `workspace-write` 退出后 ACL 残留检查 | 残留可观测并有清理方案 |
---
## 5. CI 门禁(分层)
**新增 workflow**`.github/workflows/sandbox-gates.yml`
### 5.1 PR 必跑
| Job | 说明 |
|-----|------|
| `sandbox-unit` | matrix: ubuntu/macos/windows运行 `src/main/services/sandbox/` 单元测试 |
| `sandbox-matrix-consistency` | `generate-sandbox-matrix-doc.js` 生成后 `git diff --exit-code`,不一致则失败 |
| `sandbox-integration-unix` | matrix: ubuntu/macos运行 `tests/sandbox-integration/` |
### 5.2 Release 分支必跑
| Job | 说明 |
|-----|------|
| `sandbox-integration-windows` | `runs-on: windows-latest`,运行 WN-01~WN-09 可自动化子集 |
| `sandbox-security-report` | 汇总所有 job 产物,生成 `sandbox-report.json` artifact |
### 5.3 门禁阈值
- `system_path_write` / `outside_workspace_write` critical 用例100% block
- `env_leak_count == 0`ENV-01~ENV-03
- `degrade_to_none_count > 0` 时必须有对应 `sandbox:unavailable` 事件日志
---
## 6. 交付物一览
| 文件 | 类型 | 说明 |
|------|------|------|
| `docs/sandbox/sandbox-matrix.spec.json` | 规范(手工维护) | 沙箱层单一真源 |
| `docs/sandbox/permission-matrix.spec.json` | 规范(手工维护) | 命令权限层单一真源 |
| `docs/sandbox/sandbox-matrix.generated.md` | 生成CI 检查) | 从 spec 生成,人类可读 |
| `crates/agent-electron-client/scripts/generate-sandbox-matrix-doc.js` | 工具 | spec.json → .md 单向转换 |
| `crates/agent-electron-client/scripts/generate-sandbox-report.js` | 工具 | 测试结果 → sandbox-report.json |
| `src/main/services/sandbox/PermissionManager.test.ts` | 测试(新增) | 命令权限层专项 |
| `tests/sandbox-integration/matrix-driven.test.ts` | 测试(新增) | 从 spec 驱动的沙箱层集成测试 |
| `tests/sandbox-integration/env-leak.test.ts` | 测试(新增) | 环境变量泄露测试 |
| `tests/sandbox-integration/fallback-observability.test.ts` | 测试(新增) | 降级可观测性测试OBS-01~03 |
| `.github/workflows/sandbox-gates.yml` | CI新增 | 分层门禁 workflow |
---
## 7. 执行顺序macOS → Windows → Linux
### Phase 1macOSWeek 1~2
1. **Week 1**:手工编写 `sandbox-matrix.spec.json`macOS seatbelt 为第一批 verdict`sudo` conditional 条目)
2. **Week 1**:新增 `PermissionManager.test.ts``allowed` 字段断言、word-boundary 回归;平台无关,最早可并行)
3. **Week 1**:新增 `fallback-observability.test.ts`OBS-01~03平台无关最早可并行
4. **Week 2**:新增 `env-leak.test.ts` ENV-01~ENV-02terminal/create + sandboxed-bash-mcp
5. **Week 2**:实现 `matrix-driven.test.ts`(先只运行 macOS 部分);打通 macOS seatbelt 集成门禁
### Phase 2WindowsWeek 3
6. 补写 `sandbox-matrix.spec.json` Windows 部分conditionalcwd 约束、网络 best-effort、sandboxed-bash-mcp `--no-write-restricted`
7. 实现 WN-01~WN-05基础行为workspace write、read-only、run/serve
8. ENV-03 + WN-06~WN-09安全回归网络旁路、ACL 持久化清理)
9. 打通 `sandbox-integration-windows.yml`release 分支门禁)
### Phase 3LinuxWeek 4
10. 修复 `linux-bwrap.integration.test.ts` callback API 误用(改为 `promisify(exec)``execa`
11. 补写 `sandbox-matrix.spec.json` Linux bwrap 部分;扩展 `matrix-driven.test.ts`
12. 打通全平台 `sandbox-gates.yml`;生成 `sandbox-report.json`
---
## 8. 已锁定假设
1. GUI Agent 沙箱不纳入本计划。
2. 采用双层模型(沙箱层 + 命令权限层),两层矩阵严格分离,不混入同一 spec 文件。
3. 交付形式固定为 JSON + Markdown + 自动化测试。
4. CI 采用分层门禁PR 强制单元测试 + 一致性检查Windows 集成在 release 分支强制。
5. 矩阵为规范normative文档人工维护生成脚本仅做 spec→md不从实现反向提取。
6. `docker` 全平台显式标注 `unsupported`,不伪装为已支持。
7. `autoFallback=session``startup-only` 当前等价,矩阵注释标明"预留枚举TBD"。
8. Windows 的 `mode`strict/compat/permissive`windowsMode`read-only/workspace-write是正交组合矩阵条目需覆盖两个维度。
9. CONC并发、SOAK长稳、LEAK资源泄漏测试不纳入本计划作为后续独立专项。
---
## 9. 与现有文档的关系
- **`sandbox-plan.md`**:保留为"问题发现与决策记录"文档,本计划不替换它。本计划将其 §12 中的 **ENV 测试用例**和 §13CI 建议)转化为可执行代码与 workflow。§12 中的 CONC并发、SOAK长稳、LEAK资源泄漏测试用例当前不纳入本计划作为后续独立专项跟进。
- **`sandbox/TEST-PLAN.md`**、**`sandbox/CODE-REVIEW.md`**:已有内容仍有效,本计划以 spec + 表驱动测试为主干,与之互补。
- **`sandbox-matrix.spec.json`**:作为"当前正确行为"的规范真源,与 `sandbox-plan.md` 中的问题列表形成问题→修复→验证闭环。
---
## 10. Deep Research 发现(实施前需评估)
以下问题在源码审查中发现,需在 Phase 1 实施前决定处置方式(修复 or 标注为已知限制):
### 10.1 PermissionManager 层
| # | 问题 | 严重度 | 源码位置 | 建议 |
|---|------|--------|----------|------|
| DR-1 | `/etc/hosts` 不在 `sensitiveEtcPaths` 列表中,当前不会被拦截 | 中 | PermissionManager.ts:651-657 | 评估是否补入;若不补入则在矩阵中标注为 `allow` |
| DR-2 | `.ssh` 检测用 `lowerTarget.includes(".ssh")`,会误拦含 `.ssh` 子串的非 SSH 路径(如 `not.sshrc` | 中 | PermissionManager.ts:642 | 改为路径段匹配(如 `/.ssh/``path.basename` 检查) |
| DR-3 | `workspaceOnly` 在 PermissionManager 中为注释占位,实际由 WorkspaceManager 执行路径边界检查 | 高 | PermissionManager.ts:203-205 | 测试需跨模块验证PermissionManager + WorkspaceManager 联合),不能仅测 PermissionManager |
### 10.2 沙箱层
| # | 问题 | 严重度 | 源码位置 | 建议 |
|---|------|--------|----------|------|
| DR-4 | Linux permissive 模式走独立代码分支,不执行 `--unshare-net`(即使 `networkEnabled=false` | 中 | SandboxInvoker.ts:228-239 | 在矩阵中标注为 conditional若需修复则在 permissive 分支末尾补入 `--unshare-net` 判断 |
| DR-5 | macOS strict/compat 下 signal 规则为 `(allow signal (target self))`permissive 为无限制 `(allow signal)` | 低 | SandboxInvoker.ts:428,464 | 在矩阵中记录差异即可 |
| DR-6 | Windows `serve` 模式固定 `write_restricted=false`main.rs:585仅靠 DACL ACE 保护写权限 | 中 | windows-sandbox-helper/src/main.rs:585 | 已知设计决策,需在矩阵 conditional 中标注 |
| DR-7 | `sandboxed-bash-mcp` 使用 `{ ...process.env }` 仅删除 `ELECTRON_RUN_AS_NODE`,所有其他环境变量透传至沙箱 | 高 | resources/sandboxed-bash-mcp/sandboxed-bash-mcp.mjs:197-202 | ENV-02 测试覆盖;长期应补入 safeKeys 白名单过滤 |
| DR-8 | Windows helper (`env.rs`) 无独立 safeKeys 白名单,以 `std::env::vars()` 全量继承再修改 | 中 | windows-sandbox-helper/src/main.rs:256, env.rs:100-135 | ENV-03 测试覆盖;与 DR-7 一起作为 env 安全专项跟进 |