现象(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>
12 KiB
安全边界(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。
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 下发,仅引用与审批结果):
"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)。注册(WSregister消息携带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 应用)由 PodsecretKeyRef引用,绝不内联进 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 开关就能开」的默认能力。需要两道独立确认:
ENABLE_QUALITY_EVAL=1—— 打开质量评测功能;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 策略)。