diff --git a/dnfs/dnfs.py b/dnfs/dnfs.py index 80aa446..4e5d8f7 100755 --- a/dnfs/dnfs.py +++ b/dnfs/dnfs.py @@ -309,6 +309,15 @@ apply_security_headers(app) CORS(app) +# [SECURITY-AUDIT] P0-1 / P1-5 —— 已修复(审计结论见 docs/security/dnfs_SECURITY_AUDIT_20260801.md) +# 原问题: +# · P0-1 Token 无 RSA 签名验证,攻击者可伪造任意身份(含管理员)。 +# · P1-5 Token 无过期校验,被盗 Token 可长期复用。 +# 修复方式(本项目统一安全决策):本服务不持有密钥,Token 校验全部上移至 gateway。 +# decode_token 转发 gateway /whoami,由 gateway 完成 RSA 验签 + 基于 +# created_at+keep_time 的过期校验;gateway 不可达 → fail-closed 抛 503, +# 验签失败/未登录 → 返回 None(调用方转 401)。is_admin 由 gateway 基于 +# [Admin] 白名单在 Token payload 写入,本服务只读,不再本地判定。 def decode_token(token: str): """通过 gateway /whoami 接口验证 Token 并返回用户信息。 @@ -465,6 +474,12 @@ def get_notes(): # eg: {"id": 1} +# [SECURITY-AUDIT] P0-3 / P1-10 —— 已修复 +# 原问题: +# · P0-3 delete_note / update_note 原缺 @_handle_exp 装饰器,异常冒泡成 Flask 默认 500 页。 +# · P1-10 上述函数内 decode_token 等调用无 try-except,异常直接裸抛 500。 +# 修复:两函数均加 @_handle_exp(定义见 L294),DnfsExp → 对应状态码、 +# 其余 Exception → 500,统一返回结构化 JSON,不再泄露栈信息。 @app.route('/delete_note', methods=['POST']) @_handle_exp def delete_note(): @@ -568,6 +583,7 @@ def delete_note(): # "title": "test", # "content": "hello" #} +# [SECURITY-AUDIT] P0-3 / P1-10 —— 同 delete_note,已加 @_handle_exp 统一异常(见上)。 @app.route('/update_note', methods=['POST']) @_handle_exp def update_note(): diff --git a/docs/INDEX.md b/docs/INDEX.md index c4a40df..723484e 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -43,8 +43,8 @@ docs/ | db | [db_SECURITY_AUDIT_20260801.md](security/db_SECURITY_AUDIT_20260801.md) | Python 数据库服务(pymsg、SQLAlchemy、日志收集) | | db2 | [db2_SECURITY_AUDIT_20260806.md](security/db2_SECURITY_AUDIT_20260806.md) | C++20 数据库 RPC 服务(db 的 C++ 重实现,SQLite C API) | | pymsg | [pymsg_SECURITY_AUDIT_20260801.md](security/pymsg_SECURITY_AUDIT_20260801.md) | Python RPC 基础库(长度前缀帧、MsgClient/Server) | -| dnfs | [dnfs_SECURITY_AUDIT_20260801.md](security/dnfs_SECURITY_AUDIT_20260801.md) | Python 树状菜单服务 | -| dnfs2 | [dnfs2_SECURITY_AUDIT_20260808.md](security/dnfs2_SECURITY_AUDIT_20260808.md) | C++20 树状菜单服务(dnfs 重实现) | +| dnfs | [dnfs_SECURITY_AUDIT_20260801.md](security/dnfs_SECURITY_AUDIT_20260801.md) | Python 树状网络文件系统(**已废弃,生产用 dnfs2**;2026-08-10 复核为全量 16 项发现) | +| dnfs2 | [dnfs2_SECURITY_AUDIT_20260808.md](security/dnfs2_SECURITY_AUDIT_20260808.md) | C++20 树状网络文件系统(dnfs 重实现,**生产在跑**,评级 A) | | static | [static_SECURITY_AUDIT_20260801.md](security/static_SECURITY_AUDIT_20260801.md) | Python 前端托管/文件服务 | | fsys | [fsys_SECURITY_AUDIT_20260808.md](security/fsys_SECURITY_AUDIT_20260808.md) | Python 文件系统服务 | | ai_review | [ai_review_SECURITY_AUDIT_20260801.md](security/ai_review_SECURITY_AUDIT_20260801.md) | Python AI 内容审核(DeepSeek + RAG) | diff --git a/docs/security/dnfs2_SECURITY_AUDIT_20260808.md b/docs/security/dnfs2_SECURITY_AUDIT_20260808.md index 029ae4a..7b3bca9 100644 --- a/docs/security/dnfs2_SECURITY_AUDIT_20260808.md +++ b/docs/security/dnfs2_SECURITY_AUDIT_20260808.md @@ -6,6 +6,8 @@ **审计轮次**: 第 2 轮(第 1 轮发现 5 项已全部修复) **安全评级**: A +> 📌 **本服务为 dnfs 的 C++20 生产实现**,`core/init.txt` 实际启动的是 `dnfs2`(Python dnfs 已废弃,历史审计见 [`dnfs_SECURITY_AUDIT_20260801.md`](dnfs_SECURITY_AUDIT_20260801.md),2026-08-10 已复核为全量 16 项发现)。本审计覆盖生产代码,结论有效。 + --- ## 1. 审计概述 diff --git a/docs/security/dnfs_SECURITY_AUDIT_20260801.md b/docs/security/dnfs_SECURITY_AUDIT_20260801.md index 5cc8eeb..eef920b 100644 --- a/docs/security/dnfs_SECURITY_AUDIT_20260801.md +++ b/docs/security/dnfs_SECURITY_AUDIT_20260801.md @@ -1,112 +1,156 @@ -# dnfs 模块安全审计报告 +# dnfs 模块安全审计报告(Python 版) -**审计日期**: 2026-08-01 -**审计范围**: `dnfs.py`、`security_middleware.py`、`log_client.py` -**代码版本**: dev_qing 分支 +**原始审计日期**: 2026-08-01 +**代码实证复核(最后更新)**: 2026-08-10 +**审计范围**: `dnfs/dnfs.py`(723 行)、`security_middleware.py`、`log_client.py` +**代码版本**: dev_qing 分支 +**历史评级**: B(2026-08-01 审计) --- -## 代码验证记录 - -**验证日期**: 2026-08-03 -**验证方式**: 逐条对照审计报告与当前代码(`dnfs.py`、`security_middleware.py`、`log_client.py`) - -### 验证结果汇总 - -| 等级 | 总数 | 已修复 | 部分修复 | 未修复 | 修复率 | -|------|------|--------|----------|--------|--------| -| P0 | 1 | 1 | 0 | 0 | 100% | -| P1 | 2 | 2 | 0 | 0 | 100% | -| P2 | 2 | 0 | 0 | 2 | 0% | -| **合计** | **5** | **3** | **0** | **2** | **60%** | - -### 逐条验证 - -| 编号 | 状态 | 代码位置 | 验证说明 | -|------|------|----------|----------| -| P0-1 | ✅ 已修复 | `dnfs.py` L312-351 | `decode_token` 完全重写:不再本地解码,改为调用 gateway `/whoami` 接口,由 gateway 完成 RSA 签名验证和过期检查;网络异常 fail-closed(返回 503) | -| P1-1 | ✅ 已修复 | `dnfs.py` L312-351 | Token 过期校验由 gateway `/whoami` 统一处理,dnfs 不再自行校验 | -| P1-2 | ✅ 已修复 | `dnfs.py` L348-351 | `is_admin` 由 gateway 服务端判定(基于 Admin 白名单),dnfs 信任 gateway 响应,不再从用户提交的 Token 中直接读取 | -| P2-1 | ❌ 未修复 | `dnfs.py` L156-202 | `review_content` 已实现 fail-closed(所有异常路径均拒绝请求),但返回类型不一致(bool vs dict),需代码清理 | -| P2-2 | ❌ 未修复 | `dnfs.py` L468-562 | `delete_note` 无确认机制,无速率限制 | +> ## ⚠️ 废弃声明 +> +> **Python dnfs 已废弃,生产环境实际运行的是 C++20 重写版 `dnfs2`**(见 [`dnfs2_SECURITY_AUDIT_20260808.md`](dnfs2_SECURITY_AUDIT_20260808.md),评级 **A**)。`core/init.txt` 第 3 行启动的是 `dnfs2/_run.sh`,Python dnfs 不在运行清单内。 +> +> 本审计仅覆盖 **Python dnfs 的历史代码状态**,用于溯源与和 dnfs2 的修复对照。**不要在线上启用 Python dnfs**——它的多数 P0/P1 安全问题未修复(见下方清单),且这些问题在 dnfs2 中已全部解决。 --- -## 修复进度 +## 1. 模块定位与状态 -| 优先级 | 总数 | 已修复 | 待修复 | -|--------|------|--------|--------| -| P0 | 1 | 1 | 0 | -| P1 | 2 | 2 | 0 | -| P2 | 2 | 0 | 2 | +dnfs = **Dendrachy Network File-System**,基于父子关系(`pid`)组织的树状节点服务,监听 `127.0.0.1:8006`,提供节点的增删改查,写操作需 gateway 签发的 Token。 ---- +- **Python dnfs(本审计对象)**:Flask + Waitress,历史实现,已废弃。 +- **C++ dnfs2(生产实现)**:libhttp + libmsg + boost::json,接口兼容,安全基线显著提升(评级 A)。 -## 🔴 P0 — 严重问题 +接口(4 个 `POST`):`get_notes`(无鉴权)、`add_note`、`delete_note`、`update_note`(后三者需 Bearer Token;add/update 写入前经 `ai_review` 内容审核)。 -### P0-1: Token 无 RSA 签名验证,可伪造任意用户身份(含管理员) ✅ 已修复 -- **位置**: `dnfs.py:312-351`(完全重写) -- **原问题**: `decode_token` 本地 base64 解码 Token,代码明确标注 `FIXME: 验证数字签名`,但完全跳过了 RSA 签名验证,攻击者可伪造任意用户身份(含管理员) -- **修复方案**: `decode_token` 完全重写,改为调用 gateway `/whoami` 接口: - 1. 将 Token 通过 `Authorization: Bearer` 头发送给 gateway - 2. Gateway 服务端完成 RSA 签名验证 + 过期检查 - 3. 返回 `{"login": true/false, "user": "...", "is_admin": true/false, ...}` - 4. 网络异常(gateway 不可达)→ fail-closed,返回 503 - 5. 验签失败(`login=false`)→ 返回 None,调用方返回 401 -- **影响范围**: 已消除,所有需要认证的端点(add_note、delete_note、update_note)均通过 gateway 验证 +## 2. 架构与数据流 ---- +``` +客户端 → waf:443 → ca:8443(认证) → gateway:8445(纯转发, loopback) + → dnfs2:8006 ──pymsg──► db2:8005 (execute 路由, 落 SQLite Notes 表) + └──HTTP──────► ai_review:8007 (add/update 写入前内容审核) +``` -## 🟠 P1 — 高危问题 +- **Token 验签**:dnfs 不持有密钥,统一转发 `gateway /whoami` 由 gateway 完成 RSA 验签 + 过期校验;gateway 不可达 → fail-closed 返回 503。 +- **数据操作**:经 pymsg RPC 调用 db 服务 `_route: "execute"`,不直接连库(规避 SQL 注入)。 +- **特殊节点(管理员专属)**:`content` 为 `{"_sp":"link|js|image|file",...}` 信封时前端差异化渲染;仅 `is_admin` 可发布。 -| # | 问题 | 状态 | 说明 | -|---|------|------|------| -| P1-1 | Token 过期校验不严谨 | ✅ 已修复 | Token 过期校验由 gateway `/whoami` 统一处理(基于 `created_at` + `keep_time`),dnfs 不再自行校验 | -| P1-2 | is_admin 权限完全依赖 Token 字段,无二次验证 | ✅ 已修复 | `is_admin` 由 gateway 服务端基于 Admin 白名单判定,dnfs 从 gateway 响应中读取(`info.get('is_admin')`),不再从用户提交的 Token 直接读取 | +## 3. 审计结论与评级 ---- +| 等级 | 原始审计(2026-08-03 验证) | 当前代码实证(2026-08-10) | +|------|---------------------------|---------------------------| +| P0 | 1 项,已修复 1 | 2 项:部分修复 1,未修复 1* | +| P1 | 2 项,已修复 2 | 5 项:未修复 5 | +| P2 | 2 项,未修复 2 | 5 项:全部未修复 | -## 🟡 P2 — 中危问题 +\* P0-4(父节点删除 TOCTOU)在 Python dnfs 中仍**未修复**,但其"未先检查 `success`"的子问题在 dnfs2 中已修,仅残余 TOCTOU 窗口(需 db2 事务支持)。 -| # | 问题 | 状态 | 说明 | -|---|------|------|------| -| P2-1 | AI 审核 fail-closed 实现需验证 | ❌ 未修复 | `review_content` 已实现 fail-closed(HTTPError/URLError/JSONDecodeError 均导致请求被拒),但函数返回类型不一致(bool vs dict),建议代码清理 | -| P2-2 | 节点删除无确认机制,批量删除无速率限制 | ❌ 未修复 | `delete_note` 无二次确认,无速率限制 | +**评级说明**:Python dnfs 历史评级为 **B**(基于 2026-08-01 仅 5 项发现的快照)。按当前代码实证的全量 16 项发现,若仍上线则应下调评级——其 P0-4(树结构完整性)、P1-6/7/8/9/11(请求体、配置一致性、父节点校验、启动等待、类型转换)等高危问题均未修复。**正确做法是不再启用 Python dnfs,统一使用 dnfs2。** ---- +## 4. 完整发现清单(代码实证,2026-08-10) -## ✅ 已有安全机制 +> 编号沿用 `dnfs/README.md`「已知问题与风险」的权威枚举(P0-1~4 / P1-5~11 / P2-12~16,共 16 项)。 +> 状态依据对 `dnfs/dnfs.py` 与 `log_client.py` 的逐行核对。 +> **已合入代码注释(dnfs.py)并移出本节的项**:P0-1、P0-3、P1-5、P1-10(均为 ✅ 已修复/已解决),其安全决策见对应代码注释。本节仅保留未修复/部分修复的开放项,共 12 项。 -1. ✅ **特殊节点校验**: link/html/image/file 各类型有格式与大小限制(URL 前缀、大小上限) -2. ✅ **AI 内容审核集成**: 新增节点前调用 ai_review 服务审核 -3. ✅ **security_middleware.py**: 安全响应头(CSP、X-Frame-Options、X-XSS-Protection 等) -4. ✅ **CORS 配置**: 跨域控制 -5. ✅ **通过 pymsg 调 db 服务**: 不直连数据库,规避 SQL 注入 +### 🔴 P0 — 严重问题 -## 新增安全机制(审计后添加,审计报告未覆盖) +| 编号 | 问题 | 状态 | 代码位置 | 说明 | +|------|------|------|----------|------| +| P0-2 | `review_content()` fail-closed 与返回类型不一致 | ⚠️ 基本修复 | `dnfs.py:156-202` | 异常路径(HTTPError/URLError/JSONDecodeError)均拒绝请求;但 HTTPError 且响应体为合法 JSON 时返回 **dict**(L196 `return json.loads(error_body)`),与成功路径返回的 **bool** 类型不一致,存在 `True != dict` → 一律拦截(仍 fail-closed,但类型契约不干净,建议清理) | +| P0-4 | 有子节点的父节点可能被删除(树结构完整性) | ❌ 未修复 | `dnfs.py:510-525` + `db/create_sqlite3.sql:13-19` | 应用层有子节点检查(`if len(notes['data']) > 0: return 400`),但:① **数据库层 `Notes.pid` 无外键约束**;② 子节点查询与删除间存在 **TOCTOU 竞态**;③ `notes['data']` 在 **未先确认 `success`** 时访问(L519),`success=False` 时抛 KeyError | -| 机制 | 代码位置 | 说明 | -|------|----------|------| -| Token 验签委托 gateway | `dnfs.py` L312-351 | `decode_token` 调用 gateway `/whoami` 完成 RSA 签名验证 + 过期检查,dnfs 不再持有/管理密钥 | -| Token 验签 fail-closed | `dnfs.py` L336-339 | gateway 不可达时返回 503,拒绝所有认证请求 | -| 配置白名单合并 | `dnfs.py` L55-63 | `_merge_ini` 仅覆盖已知 section/key,防止意外字段注入 | -| 配置完整性校验 | `dnfs.py` L100-112 | 启动时检查所有必需配置项,缺失则退出 | -| 特殊节点审核脱敏 | `dnfs.py` L273-286 | `_sp_review_text` 剔除 base64 负载(体量大且无审核意义),仅保留文本字段送审 | -| 统一异常处理 | `dnfs.py` L294-305 | `_handle_exp` 装饰器统一捕获 DnfsExp 和通用异常,返回结构化错误响应 | -| 异步日志 fail-safe | `log_client.py` L66-72, L147-148 | AsyncRpcQueue 后台异步推送,db 不可用时静默丢弃,不阻塞业务 | +### 🟠 P1 — 高危问题 ---- +| 编号 | 问题 | 状态 | 代码位置 | 说明 | +|------|------|------|----------|------| +| P1-6 | 请求体大小无限制 | ❌ 未修复 | `dnfs.py:723` | `serve(app, host=_ADDR, port=_PORT)` 未配置 `max_request_body_size`,建议与 WAF 对齐设 10MB | +| P1-7 | `log_client.py` 配置与主模块不一致 | ❌ 未修复 | `log_client.py:32-33` | 日志客户端直读 `DB_HOST`/`DB_PORT` 环境变量,不拉取 confs,与主模块配置源不一致(dnfs2 已复用主配置修复) | +| P1-8 | `update_note` 改 pid 时未校验目标父节点存在 | ❌ 未修复 | `dnfs.py:571-673` | `update_note` 直接更新 `pid`,当 `pid≠0` 时不检查父节点是否存在(dnfs2 已用 `_note_exists` 校验) | +| P1-9 | db RPC 客户端未启用启动等待 | ❌ 未修复 | `dnfs.py:133` | `_rpc = RpcClient(_DBV2_ADDR, _DBV2_PORT, "dnfs")` 无 `retry_on_refused=True`;db 未就绪时相关 API 直接失败(dnfs2 已加 `retry_on_refused=true`) | +| P1-11 | `note_id` 缺少强制类型转换 | ❌ 未修复 | `dnfs.py:474,577` | 仅类型提示 `note_id: int = data.get('id')` / `id: int = data.get('id')`,依赖 SQLite 类型亲和性隐式匹配(dnfs2 已用 `get_int()` + `is_int64()` 强校验) | -## 修复路线图 +### 🟡 P2 — 中危问题 -### 立即(24小时) -- [x] 实现 Token RSA 签名验证 ✅ 已完成(委托 gateway) +| 编号 | 问题 | 状态 | 代码位置 | 说明 | +|------|------|------|----------|------| +| P2-12 | 错误响应泄露内部信息 | ❌ 未修复 | `dnfs.py:371,395,400,420,523,540,561,615` | 错误消息拼接 `request.remote_addr` 与用户名返回客户端(如 `未授权访问,从{ip}`、`特殊节点仅管理员可发布,用户{user},从{ip}`);应仅记服务端日志(dnfs2 已清除此类泄露) | +| P2-13 | CORS 配置过于宽松 | ❌ 未修复 | `dnfs.py:309` | `CORS(app)` 默认允许所有来源(`*`),建议限制为实际前端域名 | +| P2-14 | `_call_dbv2` 返回值访问前未统一检查 success | ❌ 未修复 | `dnfs.py:387,444,519,537` | 多处直接访问 `notes['data']` 而不先确认 `success==True`,db 业务错误时可能 KeyError | +| P2-15 | `pid` 参数判断逻辑不严谨 | ❌ 未修复 | `dnfs.py:432` | `if not pid: pid = 0` 将空字符串/None/0 等非法输入默默转为 0,掩盖参数错误 | +| P2-16 | 遗留注释代码 | ❌ 未修复 | `dnfs.py:17,491-507,636-648,677-711,715-718` | 大量注释掉的旧版 SQLAlchemy 直连逻辑与已废弃接口,影响可读性 | -### 短期(72小时) -- [x] Token 过期严格校验 ✅ 已完成(gateway 统一处理) -- [x] is_admin 二次验证 ✅ 已完成(gateway 服务端判定) +## 5. 历史审计记录(2026-08-01 / 2026-08-03,作溯源) + +原始审计仅覆盖 5 项发现,其验证结论如下(与第 4 节对应编号映射): + +| 原编号 | 原问题 | 状态 | 映射至本章 | +|--------|--------|------|-----------| +| P0-1 | Token 无 RSA 签名验证 | ✅ 已修复 | 已合入代码注释 (dnfs.py) | +| P1-1 | Token 过期校验不严谨 | ✅ 已修复 | 已合入代码注释 (dnfs.py) | +| P1-2 | is_admin 权限完全依赖 Token 字段 | ✅ 已修复 | 已并入 P0-1(gateway 服务端基于 Admin 白名单判定) | +| P2-1 | AI 审核 fail-closed 实现需验证 | ❌ 未修复(类型不一致) | P0-2 | +| P2-2 | 节点删除无确认机制 / 无速率限制 | ❌ 未修复 | P0-4(及一般加固项) | + +原始审计的「新增安全机制」表中以下项经代码实证仍然成立: +- Token 验签委托 gateway(`dnfs.py:312-351`);fail-closed 503(`dnfs.py:336-339`) +- 配置白名单合并(`dnfs.py:55-63`);配置完整性校验(`dnfs.py:100-112`) +- 特殊节点审核脱敏(`dnfs.py:273-286`) +- 统一异常处理 `_handle_exp`(`dnfs.py:294-305`) +- 异步日志 fail-safe(`log_client.py:66-72,147-148`) + +## 6. db 错误处理一致性矩阵(Python dnfs) + +Python dnfs 多处直接访问 `notes['data']` 而未先确认 `success`,与 dnfs2 的「db 业务错误统一返回 500」形成对比: + +| 位置 | 查询目的 | 行为 | 风险 | +|------|---------|------|------| +| `add_note` 父节点检查 | L387 `notes['data']` | 未查 success | success=False 时 KeyError | +| `get_notes` | L444 `notes['data']` | 未查 success | success=False 时 KeyError | +| `delete_note` 子节点检查 | L519 `notes['data']` | 未查 success | success=False 时 KeyError | +| `delete_note` 所有权检查 | L537 `notes['data']` | 未查 success | success=False 时 KeyError | + +> dnfs2 已将全部 select 的 db 业务错误统一返回 500(见 `dnfs2_SECURITY_AUDIT_20260808.md` §6 矩阵),此项在 dnfs2 已闭合。 + +## 7. 与 dnfs2 的修复对照 + +| 本章编号 | Python dnfs 状态 | dnfs2 状态 | +|---------|-----------------|-----------| +| P0-2 | ⚠️ 基本修复 | ✅ 已修复(返回类型干净) | +| P0-4 | ❌ 未修复 | ⚠️ success 检查已修,TOCTOU 残余(L-2) | +| P1-6 | ❌ 未修复 | ✅ 已修复(`setMaxBodySize(10MB)`) | +| P1-7 | ❌ 未修复 | ✅ 已修复(日志复用主配置) | +| P1-8 | ❌ 未修复 | ✅ 已修复(pid≠0 校验父节点) | +| P1-9 | ❌ 未修复 | ✅ 已修复(`retry_on_refused=true`) | +| P1-11 | ❌ 未修复 | ✅ 已修复(`get_int()` 强类型) | +| P2-12 | ❌ 未修复 | ✅ 已修复(清除 IP/用户名泄露) | +| P2-13 | ❌ 未修复 | 记录(前端架构限制 L-7) | +| P2-14 | ❌ 未修复 | ✅ 已修复(`_call_db` 统一查 success) | +| P2-15 | ❌ 未修复 | ✅ 已修复(严格类型 + 负数校验) | +| P2-16 | ❌ 未修复 | ✅ 已清理 | + +**结论**:dnfs2 基本闭合了 Python dnfs 的全部 P0/P1 及大部分 P2;Python dnfs 的未修复项无需在 Python 版继续投入,统一以 dnfs2 为准。 + +## 8. 修复路线图(仅针对需要继续维护 Python dnfs 的极端场景) + +### 立即(如被迫启用 Python dnfs) +- [ ] P0-4:Notes 表 `pid` 加 `FOREIGN KEY ... REFERENCES Notes(id) ON DELETE RESTRICT`;应用层先查 `success` 再访问 `data` +- [ ] P1-6:`serve()` 配置 `max_request_body_size=10MB` +- [ ] P2-14:封装统一的 db 调用检查 `success` + +### 短期 +- [ ] P1-7:日志客户端复用 confs 主配置 +- [ ] P1-8:`update_note` 改 pid 前校验父节点存在 +- [ ] P1-9:`RpcClient` 启用 `retry_on_refused=True` +- [ ] P1-11:`note_id` 显式 `int()` 转换 + 校验 +- [ ] P2-12:错误消息去除 IP/用户名,仅记日志 ### 中期 -- [ ] 清理 `review_content` 返回类型不一致(bool vs dict) -- [ ] 节点操作速率限制 -- [ ] 节点删除二次确认机制 +- [ ] P2-13:CORS 限制为实际前端域名 +- [ ] P2-15:`pid` 参数严格校验(拒绝空串/None) +- [ ] P2-16:清理遗留注释代码 +- [ ] P0-2:清理 `review_content` 返回类型不一致 + +> **推荐替代方案**:直接弃用 Python dnfs,全部流量走 dnfs2(已在 `core/init.txt` 生效)。