# 外网攻击面加固 Backlog（audit_only 产出）

**日期**：2026-06-06  
**来源**：[`2026-06-06-external-attack-surface-audit.md`](2026-06-06-external-attack-surface-audit.md)  
**状态**：BL-01～BL-07 **已实施/已接受**；BL-08 **不需要**（运维确认）；BL-06 **已实施**（2026-06-07）

---

## P2 — 建议优先

### BL-01 收紧 `PUBLIC_PREFIXES` 前缀匹配 ✅ 已实施

| 字段 | 内容 |
|------|------|
| 问题 | EXT-01 `/health/detail`、EXT-02 `bing-wallpapers/sync` 被前缀误放行 |
| 方案 A | 将 `/health` 改为精确路径列表；`/api/settings/bing-wallpapers` 仅匹配 GET（或拆路由前缀） |
| 方案 B | `_is_public_path` 改为精确匹配 + 显式允许方法 |
| 涉及文件 | `server/api/auth_jwt.py` |
| 验收 | 无 JWT 时 `GET /health/detail` → 401；`POST .../sync` → 401 |
| 回滚 | 恢复 `PUBLIC_PREFIXES` 元组 |

### BL-02 `/health/detail` 强制鉴权（防御纵深） ✅ 已实施

| 字段 | 内容 |
|------|------|
| 问题 | 非 `/api/` 路径不走 JwtAuthMiddleware |
| 方案 | handler 内显式 `if admin is None: raise 401`；或把 detail 移到 `/api/health/detail` |
| 涉及文件 | `server/api/health.py`、`server/main.py` DbSessionMiddleware 路径列表 |
| 验收 | 生产探测 401 |

### BL-03 `bing_wallpapers_sync` 拒绝 `admin is None` ✅ 已实施

| 字段 | 内容 |
|------|------|
| 方案 | `Depends(get_current_admin)` 返回 None 时 raise 401（与 BL-01 双保险） |
| 涉及文件 | `server/api/settings.py` |

---

## H — 信息暴露与运维

### BL-04 生产关闭 OpenAPI / Swagger / ReDoc ✅ 已实施

| 字段 | 内容 |
|------|------|
| 问题 | EXT-03 全 API schema 公网可读 |
| 方案 A | `FastAPI(..., docs_url=None, redoc_url=None, openapi_url=None)` |
| 方案 B | Nginx/1Panel `location` deny 或 internal only |
| 验收 | `GET /openapi.json` → 404 |
| 备注 | 开发环境可保留文档 |

### BL-05 已安装后隐藏 install 指纹 ✅ 已实施

| 字段 | 内容 |
|------|------|
| 实现 | `_raise_not_found_if_locked()`；全部 install 端点锁定后 404 |
| 涉及文件 | `server/api/install.py`、`tests/test_install_locked_404.py` |
| 验收 | 锁定后所有 `/api/install/*` → 404 |

### BL-06 Agent per-server key 迁移 ✅ 已实施

| 字段 | 内容 |
|------|------|
| 问题 | global `API_KEY` legacy 回退（原 risk-acceptance 接受项 2） |
| 方案 | 移除 `agent.py` / `servers.py` / `script_service.py` 中 global 回退；强制 per-server key |
| 实施日期 | 2026-06-07（生产 0 台子机窗口） |
| 验收 | `tests/test_agent_per_server_key.py`；global key 打 heartbeat → 401 |
| changelog | `docs/changelog/2026-06-07-agent-per-server-key-enforced.md` |

### BL-07 WebSocket 外网探测与反代 ✅ 已澄清/接受

| 字段 | 内容 |
|------|------|
| 原问题 | 外网无 token 探测见 HTTP 403，误以为反代未转发 WS |
| 结论 | 直连 8600 同为 403；1Panel 反代已带 Upgrade；**有效 JWT 可连** `/ws/alerts`、`/ws/sync` |
| 根因 | `websocket.close(4001/4401)` 在 `accept()` 前 → 黑盒见 HTTP 403，非安全缺陷 |
| 证据 | [`2026-06-06-external-hardening-deploy-verification.md`](2026-06-06-external-hardening-deploy-verification.md) §WebSocket |
| 动作 | 更新 `security_probe_external.sh`：无 token 拒绝即 PASS；**无需改 Nginx** |

### BL-08 全站 IP 白名单 ❌ 不需要

| 字段 | 内容 |
|------|------|
| 决定 | 2026-06-06 运维确认：**不实施** |
| 理由 | 单人自用；2000+ 子机 Agent 需放行 `/api/agent/*`，全站白名单 ROI 低 |
| 维持 | 仅 login IP allowlist（`LOGIN_ALLOWLIST_ENABLED`）即可 |

---

## 验证清单（修复批次）

```bash
NEXUS_PROBE_BASE=https://api.synaglobal.vip bash scripts/security_probe_external.sh
# 期望：health/detail 401；bing sync 401；openapi 404（若 BL-04）
bash scripts/local_verify.sh
```

---

## 不建议本轮做

| 项 | 原因 |
|----|------|
| WS ticket 首帧鉴权 | 单人运维 ROI 低，已接受 |
| 登录暴破 CAPTCHA | 已有 15min 锁定 |
| 关闭 `/agent/install.sh` 公网 | 破坏远程安装流程 |
