# 审计 — 2026-06-07 BL-06 Agent per-server key 强制

## 范围

移除 Agent 认证 global `API_KEY` legacy 回退；涉及 3 个后端文件 + 1 个新测试文件。

| 文件 | 行数 | Read 范围 |
|------|------|-----------|
| `server/api/agent.py` | 379 | L40-93（认证函数） |
| `server/api/servers.py` | 2056 | L619-636, L1625-1640（安装链路） |
| `server/application/services/script_service.py` | 735 | L44-50（回调 key） |
| `tests/test_agent_per_server_key.py` | 88 | 全文 |
| `tests/load_test.py` | 202 | 顶部 docstring |
| `tests/quick_load.py` | 87 | 顶部 docstring |

## Step 3 规则扫描

| H | 规则 | 结论 |
|---|------|------|
| H1 | Agent 认证 fail-closed | FIXED 移除 global 回退；无 per-server key → False |
| H2 | 安装链路静默降级 | FIXED 无 key 明确 400/批量错误项 |
| H3 | 长任务回调 key 泄露面 | FIXED 仅用 per-server key，无 global 回退 |
| H4 | compare_digest 时序安全 | SAFE 保留 `secrets.compare_digest` |
| H5 | DB 查服异常 | SAFE 保持 fail-closed return False |

## Closure

| H | 判定 | 依据 |
|---|------|------|
| H1 | FIXED | `agent.py:60-82` 仅 `agent_api_key` 匹配 |
| H2 | FIXED | `servers.py:627-633`, `servers.py:1635-1637` |
| H3 | FIXED | `script_service.py:44-50` ValueError |
| H4 | SAFE | `agent.py:82` compare_digest |
| H5 | SAFE | `agent.py:68-76` except → False |

## 入口表（agent.py 受影响路由）

| 方法 | 路径 | 鉴权 |
|------|------|------|
| POST | `/api/agent/heartbeat` | X-API-Key + per-server `agent_api_key` |
| POST | `/api/agent/script-callback` | 同上 + job secret |

## 巡检补修（同日）

| 项 | 原问题 | 修复 |
|----|--------|------|
| H6 | `receive_heartbeat` 认证后再次 `get_server`（重复查库） | `_verify_server_api_key` 返回 `Optional[Server]`，handler 复用 |
| H7 | 验 key 通过后 `unknown_server` discarded 分支为死代码 | 删除；不存在/无 key/错 key 统一 401 |
| H8 | `script-callback` docstring 仍写「per-server or global」 | 改为仅 per-server |
| H9 | 无回归测试约束 `get_server` 调用次数 | `test_verify_server_key_single_get_server_per_auth_check` + `assert_called_once_with` |

## 生产只读探测（2026-06-07）

| 探测 | 结果 |
|------|------|
| `GET /health` | 200 |
| `POST /api/agent/heartbeat` 假 key | 401 |
| `security_probe_external.sh` heartbeat/WS | PASS |

注：BL-06 完整语义（global key 不可冒充）需部署本地改动后生产才生效；当前生产假 key 已 401。

## DoD

- [x] ruff 零错误（改动文件）
- [x] import server.main
- [x] pytest `test_agent_per_server_key.py` 9 passed（含 `assert_called_once`）
- [x] changelog ≥10 行
- [x] 无静默吞错；无 global key 降级
- [x] 心跳路径单次 `get_server`（巡检补修）
