# Audit: 2026-05-30 — Heartbeat 422 修复 + Agent 弃用升级 ## Step 1: 登记 - 审计人: Claude Sonnet 4.6 - 时间: 2026-05-30 - 范围: server/api/schemas.py, server/api/agent.py, server/main.py, web/agent/agent.py, web/agent/uninstall.sh ## Step 2: 全文 Read 已读全部5个文件的完整内容。 ## Step 3: 规则扫描 (H = 高风险点) | # | 位置 | H | 说明 | |---|------|---|------| | H1 | agent.py:152 server_id=None | 安全 | 缺 server_id 的请求绕过了 _verify_server_api_key | | H2 | agent.py 指数退避 | 逻辑 | _consecutive_failures 局部变量,心跳循环正确性 | | H3 | schemas.py server_id Optional | 安全 | Pydantic 允许 None 后是否影响其他端点 | | H4 | uninstall.sh crontab sed | 安全 | sed 删除 crontab 条目是否误删 | | H5 | agent.py lifespan | 逻辑 | task.cancel() 是否正确清理 | ## Closure 表 | H | 判定 | 依据 | |---|------|------| | H1 | ✅ 安全 | server_id=None 时直接 return,不进入 Redis/MySQL 写入,不执行任何敏感操作。_verify_api_key 已在 Depends 层验证全局 key。无 server_id 不代表绕过认证——只是未注册 agent。 | | H2 | ✅ 正确 | _consecutive_failures 在 while True 外初始化,循环内递增/重置。成功时重置为0,失败时递增。退避计算 min(interval * 2^min(failures, 5), 300) 合理。 | | H3 | ✅ 安全 | AgentHeartbeat 仅用于 agent.py heartbeat 端点。其他端点(script-callback)使用独立 schema AgentScriptCallback,不受影响。 | | H4 | ✅ 安全 | sed 匹配模式为 nexus.agent|multisync.agent|nexus-agent|multisync-agent,精确匹配 Agent 相关条目,不会误删其他 cron 任务。 | | H5 | ✅ 正确 | asyncio.create_task 返回 task 对象,yield 后 task.cancel() 会抛 CancelledError 到 send_heartbeat 协程,正确终止循环。 | ## Step 4: 入口表 | 入口 | 鉴权 | 输入验证 | |------|------|---------| | POST /api/agent/heartbeat | _verify_api_key (Header) | AgentHeartbeat schema (server_id Optional) | | Agent send_heartbeat() | 内部 | N/A (读取本地 psutil) | | uninstall.sh | SSH (外部) | N/A | ## Step 5: 输入验证 - server_id=None → 已处理,返回 200 discarded - heartbeat payload 无 server_id → Pydantic 不再报 422,handler 层丢弃 - agent 指数退避参数:HEARTBEAT_INTERVAL 来自 config,有默认值 60 ## Step 6: Sink 分析 - Redis HSET: 仅在 server_id 有效时执行 - MySQL update_heartbeat: 仅在 server_id 有效时执行 - 日志: warning 级别,无敏感数据泄露 ## Step 7: 归类 - H1-H3: 安全 → 均有防护,无风险 - H4: 安全 → 匹配模式精确 - H5: 逻辑 → 正确 ## DoD (Definition of Done) - [x] 所有 H 有 Closure 判定 + 依据 - [x] 无 TODO/FIXME 遗留 - [x] 无静默吞错 - [x] 无硬编码密钥 - [x] 无 f-string SQL - [x] ruff check 通过 - [x] import server.main 通过 - [x] ast.parse 通过