Files
Agentswarm/docs/integration/security-boundary.md
FastheiandClaude Opus 4.8 efa865cac4 fix(launcher/#63): 拉起 agent 注入 git 绑定(GIT_REPO_URL + azkv 凭据)— 修 HM #92
现象(HM #92):蜂群"需绑定 git 仓库才能使用",但 agent 实际拿不到仓库。

根因:HM 已在 swarm create 的 resource_grants 下发 git 绑定(resource_type=git +
metadata.repo_url + secret_ref=azkv://),但 swarm 拉起侧只把 grant 用于校验/脱敏/审批,
launcher 从不消费它 → 被拉起 agent 的 env 没有 GIT_REPO_URL/凭据 → 无法 clone。
(agent 侧 agent/main.py + agent/git_operations.py 早已读取这些 env,缺的纯是注入。)

修复(仅 launcher 注入,比照模型 key 的服务端解析路径):
- 新增 resolve_git_grant(body):从 resource_grants(含 per-agent)取首个 git grant,
  repo_url(metadata) → GIT_REPO_URL(非密文 inline);secret_ref(azkv) 服务端解析 →
  GIT_USERNAME/GIT_PASSWORD。复用 azkv 读取(workload identity;dev/CI 用
  HEICODE_SECRET_<name>)。有 repo 无凭据仍注入 GIT_REPO_URL(公有仓可 clone;私有仓
  报错,不伪造)。
- plan_launch_specs 增加 git_env 合并;create 路径解析并透传。
- k8s 后端:GIT_PASSWORD 与模型 key 同走 per-swarm Secret 的 secretKeyRef,绝不内联
  PodSpec;GIT_REPO_URL/GIT_USERNAME 为非密文 inline。SENSITIVE_ENV_KEYS 统一管控。
- git KV secret 值约定 JSON {"git_username","git_password"}(接受 git_token/token 形式
  + 裸 token),待 HM #92 对齐。

git 凭据不入 create 请求体/回调/日志/argv(_reject_plaintext_secrets 已覆盖
resource_grants)。

文档:runtime-contract §3.3 env 表 + 约束、security-boundary §6 增 git 绑定解析口径。
测试:scripts/test-agent-launcher.py 增 resolve_git_grant/凭据提取/k8s git secret 用例。

影响:仅 agent_swarm(Swarm/Agent + 密钥/secret_ref + 文档);不改 Manager/客户端/release/
契约状态机/计费/审计字段。HM 侧 binding_id(B 路径)解析另在 HM #92 处理,与本 PR 无关。

验收:
  python scripts/test-runtime-contract.py
  python scripts/test-contract-freeze.py
  REDIS_FAKE=1 python scripts/test-key-injection-contract.py
  python scripts/test-security-boundary.py
  python scripts/test-agent-launcher.py
  python scripts/test-git-workflow.py
  python scripts/test-merge-smoke.py
  python scripts/test-workflow-e2e.py
(全部通过)

Refs HM #92, Closes #63

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:17:51 +08:00

114 lines
12 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.
# 安全边界(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://<vault>/secrets/<name>`,**不向后兼容 `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_<name>`。解析不到即 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/<id>/` 的**留出测试**(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 策略)。