# 安全边界(Secret / Workspace / Tool / Approval / Tenant / Sandbox) > 状态:**已强制边界 FROZEN v1(可验证)+ 部分待接入**(依据 `heicode-mananger/docs/heicode.md §六/七`、`docs/heicode-runtime-auth-newapi-secret-design.md §三/五`、`docs/integration/heicode-am-contract.md §3.1/§4`)。回应 issue #19。 > > **本仓强制并可验证的边界**(契约测试 `scripts/test-security-boundary.py`,CI 守护):① secret 仅 `azkv://` 引用、明文密钥入口拒绝、回调/持久化脱敏;② workspace 写入路径越界(绝对路径 / `..` 逃逸)拒绝;③ 代码沙箱 **fail-closed**(未确认隔离则拒绝执行)。 > > **本次不扩展(确认的边界/后续层,非本仓可改)**:tool/MCP 权限引擎(**无 SK/MCP 工具层**可治理)、租户隔离(**有意**按 `user/channelId` 归因、不引入 tenant)、Pod 级强化沙箱(seccomp/只读根/NetworkPolicy,归 Infra/Security)。详见 §9。 > > 配套:[`runtime-contract.md`](./runtime-contract.md)、[`usage-billing-schema.md`](./usage-billing-schema.md)。 ## 1. 不可破坏原则 - 密钥/Token/云凭据/SSH 私钥/数据库密码/NewAPI key **不得**进入代码、日志、Markdown、前端响应或 Git。 - 凭据基线 = **Azure Key Vault**;引用一律 `azkv:///secrets/`,**不向后兼容 `vault://`**。 - Manager DB 与 Swarm 只保存 `secret_ref`,真实凭证由 Secret Broker 写入 Key Vault。 - 子 Agent 不持有长期密钥;只接收角色、资源元数据、AGENT.md 与**短期、最小权限、可审计**凭证。 - 高危操作审批**只在客户端完成**;运行时只校验审批结果,不发起审批。 ## 2. Secret 注入边界 | 项 | 标准 | 本仓状态 | |---|---|---| | `secret_ref` 前缀 `azkv://` 强校验 | 必须 | ✅ `swarm_runtime.validate_create_request` 校验 `billing_context.secret_ref` 等为 `azkv://` | | 拒绝明文密钥进入请求 | 必须 | ✅ `_reject_plaintext_secrets`(`metadata`/`resource_grants`/`callback`) | | 响应/事件/持久化脱敏 | 必须 | ✅ `_redact_sensitive` 将明文密钥键(`password`/`token`/`secret`/`private_key`/`access_key` 及 `*_token`/`*_secret`/`*_password`/`*_key`)脱敏为 `[redacted]`;**保留** `secret_ref`/`credential_ref`/`signing_secret_ref`(安全引用) | | `.env`/密钥不入库 | 必须 | ✅ `.gitignore` 忽略 `.env`、`secrets/`、`*.pem/key/p12/pfx`、`id_rsa/ed25519` | | 真实凭证写入 Key Vault(Secret Broker) | Manager 侧 | ⛔ 非本仓(Manager Secret Broker 负责) | | 短期凭证派生与注入 | 客户端审批后 | 🟡 Swarm 接收 `secret_context`;K8s 派生/注入由 Agent 平台(AM)实现 | `secret_context`(HM 下发,仅引用与审批结果): ```jsonc "secret_context": { "secret_refs": ["azkv://heicode-vault.vault.azure.net/secrets/res_git_1"], "inject_short_lived_credentials": true, "approval_id": "approval_123" } ``` ## 3. Workspace 隔离 - 每个任务在**独立按任务工作目录**执行(agent `task_workspace`)。 - 文件写入有**路径越界校验**(`task_executor._resolve_workspace_path`,拒绝绝对路径与逃逸 workspace)。 - Git 操作在仓库根(`repo_root`)执行,限定结果分支。 - 🟡 待接入:跨任务/跨租户的强隔离、只读挂载、allowed_paths 强制(当前由模型提示约束,未做运行时强制)。 ## 4. Tool / MCP 权限边界 - 当前 Agent 工具能力 = 工作区内文件读写 + Git;**无** SK/MCP 工具权限引擎。 - 🟡 待接入:统一 tool/MCP permission boundary、allowed/denied 工具策略、敏感工具审批联动(`sk_tool.*` 事件已在 schema 预留)。 ## 5. 审批门(Approval Gate) - 高危操作审批**只在客户端**;Swarm 不发起审批。 - Swarm 实现:create 命中高危(`risk_level=high` 或 `requires_user_approval`)→ 进入 `waiting_approval`,发 `approval.requested`(`approval_id`/`operation`/`risk_level`);客户端经 Manager 审批后回 `POST …/approvals/{approval_id}`(approved/rejected)→ 恢复或阻断。 - 🟡 待接入:审批主体/范围/TTL/`credential_ref`/`lease_id` 的逐项校验与到期失效,需与 Manager 审批链对齐。 ## 6. Agent 鉴权与租户隔离 - **Swarm 模型**:Agent 主动出站连编排器 WebSocket(`/ws/{agent_id}`),**不**对公网暴露每 Agent 子域名。AM 单 Agent 模型里的「客户端↔agent 直连 + `AGENT_ACCESS_TOKEN` 本地校验」**不适用于** swarm(无直连回路)。 - 服务间鉴权:HM→Swarm 用 `AGENT_RUNTIME_SERVICE_TOKEN`(Bearer);回调 HMAC 签名。 - **每用户并发 Agent 配额**:一个 `user_id` 同时连接的 Agent 数上限为 `MAX_AGENTS_PER_USER`(env,默认 10)。注册(WS `register` 消息携带 `user_id`)超额即被拒绝(回 `registration_rejected` 并关闭,code 1008),断开后释放名额。归因主轴仍为 `user.id`/`channelId`。未带 `user_id` 的 Agent 为 unbound,不计入该配额。实现:`ConnectionManager.can_bind_user/bind_user/unbind` + 注册处强制;测试 `scripts/test-max-agents-per-user.py`。 - **Swarm 拉起 agent + 服务端解析 key(team 决议,runtime-contract §3.3)**:由 **Swarm 运行时**(`orchestrator/agent_launcher.py`)拉起专家 agent 池(拉起数 `min(池大小, MAX_AGENTS_PER_USER − 已连)`,与上面的注册兜底一致)。模型 key 由 **Swarm 从 `billing_context.secret_ref`(`azkv://`)服务端解析**后注入被拉起 agent——**不入** create 请求体 / 回调 / 日志 / argv。azkv 真实解析为部署侧 SecretResolver;dev/CI 用 `HEICODE_SECRET_`。解析不到即 keyless 启动并明确报错(不伪造)。 - **Git 仓库绑定凭据服务端解析(agent_swarm#63 / HM #92)**:git 绑定经 `resource_grants` 里 `resource_type=git` 的 grant 下发。Swarm 同模型 key 方式**服务端解析**(`resolve_git_grant`):`metadata.repo_url`(非密文)→ `GIT_REPO_URL`;`secret_ref`(`azkv://` git 凭据引用)→ `GIT_USERNAME`/`GIT_PASSWORD`。git 凭据**不入** create 请求体 / 回调 / 日志 / argv;明文 git token/密码不得进 create 体(`_reject_plaintext_secrets` 已覆盖 `resource_grants`)。git KV secret 值约定 JSON `{"git_username","git_password"}`(接受 token 形式),**待 HM #92 对齐**。 - **K8s pod 边界(`AGENT_LAUNCH_BACKEND=kubernetes`,生产)**:**每 agent 一个 Pod**,带 CPU/内存 requests+limits、标签(`heicode-swarm-id`/`heicode-user-id`)。模型 key 与 **git 凭据(`GIT_PASSWORD`)** 均经**每-swarm k8s Secret**(manifest via stdin 应用)由 Pod `secretKeyRef` 引用,**绝不内联进 PodSpec env**(否则暴露于 etcd / `kubectl get pod -o yaml`);`GIT_REPO_URL`/`GIT_USERNAME` 为非密文,inline 注入。编排器需 `kubectl` + 一个仅对 `AGENT_POD_NAMESPACE` 有 pod/secret 权限的 **ServiceAccount(最小 RBAC)**;Pod 出网由 NetworkPolicy 限定到 HM `/v1` + git;停止/删除按标签 `kubectl delete pod,secret`。**更硬化(推荐 Infra 评估)**:azkv **CSI SecretProviderClass** 让 Pod 直接从 Key Vault 挂载密钥,编排器**全程不接触明文**。代码执行沙箱仍按 §8.1 在 Pod 层强制。 - 🟡 待接入:多租户运行时隔离(命名空间/网络/配额)由 Agent 平台(AKS Workload Identity)承载,非本仓编排器;归因主轴为 `user.id`/`channelId`(见 `usage-billing-schema.md`),不引入 tenant 概念。 ## 7. 外部 API 与传输 - 模型调用统一走 HM `/v1`(OpenAI 兼容),用 HM 现签 `OPENAI_API_KEY`。 - 传输:编排器/Agent 接口与回调走 **HTTPS / 私网**;`env` 含明文密钥时启动接口必须 HTTPS(`heicode-am-contract §4`)。 - 🟡 待接入:统一 external API egress 策略(白名单/出网控制)。 ## 8. 执行沙箱 - 当前执行单元为进程 / K8s Pod,隔离强度依赖部署(namespace/资源限额)。 - 🟡 待接入:强化沙箱(seccomp/只读根/网络策略/能力裁剪),由 Infra/Security Team 定义。 ### 8.1 代码测试沙箱(benchmark Group B,`orchestrator/sandbox.py`) 为给「生成代码」算真实 TestPassRate,需**执行模型生成的代码**。安全模型与边界如下: - **OS 级隔离边界 = K8s Pod / 容器**(非 root、只读根文件系统、NetworkPolicy 出网拒绝、CPU/内存/pids 限额、seccomp),由部署侧(Manager/release 清单)强制,**非本仓可改**。按 Owner 裁定,在该隔离 Pod 内运行测试代码可接受。 - **Pod 内纵深防御(本模块新增)**:每次运行用临时工作目录(结束即删);wall-clock 超时 + 进程组强杀;POSIX 资源限额(CPU 时间 / 地址空间 / 文件大小 / 子进程数,见 `SANDBOX_*` 环境变量);**环境变量清洗**(不向子进程泄漏任何 API Key/Token/云凭据/代理变量,仅放行 `PATH`/语言区域等白名单);写入路径**越界校验**(拒绝绝对路径与 `..` 逃逸);输出截断;计数从 JSON 结果文件读取,不信任 stdout。 - **Fail-closed 双重门控(PR #24 复审整改)**:代码执行不是「一个 env 开关就能开」的默认能力。需要**两道独立确认**: 1. `ENABLE_QUALITY_EVAL=1` —— 打开质量评测功能; 2. `HEICODE_SANDBOX_ISOLATED=1` —— **显式断言本进程运行在上述隔离 Pod 内**(由 Pod manifest / CI runner 设置)。 二者缺一: - 启动时:若 `ENABLE_QUALITY_EVAL` 开但隔离未确认,orchestrator **拒绝启动**(`assert_quality_eval_safe()`,main 生命周期)——平台级硬失败,非运维口头约定。 - 运行时:`sandbox.run_tests()` 与 `quality.evaluate_run_quality()` 在执行任何代码前调用 `assert_isolated()`,未确认则 **抛 `SandboxIsolationError`,不写文件、不起子进程**。 评测仅用 `benchmark/fixtures//` 的**留出测试**(held-out)作权威评分。 - **明确不构成**:本模块**不是**独立安全边界,**不替代** §8 的强化沙箱(seccomp/只读根/网络策略仍由 Infra/Security 在 Pod 层强制)。`HEICODE_SANDBOX_ISOLATED` 是「操作者确认隔离已就位」的断言,**不自行创造隔离**;只允许在真正隔离的 Pod 或 ephemeral CI/test runner 中置 1。后续可演进为专用 sandbox worker/job(复审建议的另一路径)。 ## 9. 覆盖与缺口 ✅✔ = 已实现**且有契约测试**(`scripts/test-security-boundary.py`)。 | 边界 | 状态 | |---|---| | `azkv://` `secret_ref` 强校验 / 明文拒绝 / 脱敏 / `.gitignore` | ✅✔ 已实现 + 测试(`validate_create_request`/`_reject_plaintext_secrets`/`_redact_sensitive`) | | Workspace 路径越界校验(绝对路径 / `..` 逃逸拒绝) | ✅✔ 已实现 + 测试(`task_executor._resolve_workspace_path`) | | 代码沙箱 fail-closed(`assert_isolated`:未确认隔离拒绝执行;启动 `assert_quality_eval_safe`) | ✅✔ 已实现 + 测试(§8.1 双门控 `ENABLE_QUALITY_EVAL`+`HEICODE_SANDBOX_ISOLATED`) | | 审批状态机(waiting_approval + approvals 回执) | ✅ 已实现(逐项 TTL/范围校验待加强) | | 短期凭证派生注入、Workload Identity | 🟡 AM/K8s 侧 | | Tool/MCP 权限引擎 | ⏸ 本次不做 —— **无 SK/MCP 工具层可治理**;待工具层落地后随其设计权限边界 | | allowed_paths 运行时强制(按 grant 限定)、强隔离 | ⏸ 本次不做 —— 当前仅 workspace 根越界强制;按 grant 的 allowed_paths 待资源授权链接入 | | 强化执行沙箱(seccomp/只读根/网络策略/能力裁剪,Pod 层) | ⏸ 本次不做 —— 归 Infra/Security(Pod manifest) | | 租户隔离 | ⛔ **有意**不在本仓(归因按 `user/channelId`,不引入 tenant 概念) | > ⏸ = 确认的后续/外部层,非本仓本次范围(按规则 #9 不伪造「已强制」)。本仓**已强制**的三类边界均可由 `scripts/test-security-boundary.py` 验证。 ## 10. 待对齐对象 Security / Governance Team(tool/MCP 边界、沙箱、审批逐项校验)、Infra Team(Workload Identity、租户隔离、egress 策略)。