diff --git a/dnfs2/README.md b/dnfs2/README.md index 68b748a..3974ee7 100644 --- a/dnfs2/README.md +++ b/dnfs2/README.md @@ -80,6 +80,8 @@ timeout = 5 ## 已知限制(P2,设计决策不修复) -- P2-12: 错误响应可能泄露信息(新实现已尽量脱敏) -- P2-13: CORS 反射任意 Origin(内部服务绑 127.0.0.1,可接受) -- P2-15: pid 判断已加严格类型校验,但未完全对齐 Python 语义 +- L-1: update_note 允许创建循环(需图遍历检测后代关系) +- L-2: delete_note TOCTOU 残余(需 db2 事务支持) +- L-4: 无速率限制(gateway/WAF 职责) +- L-5: title/content 无最大长度校验 +- L-6: AI 审核不可用时阻断写入(fail-closed 设计决策) diff --git a/dnfs2/src/dnfs2.cpp b/dnfs2/src/dnfs2.cpp index 53b1464..c54ffef 100644 --- a/dnfs2/src/dnfs2.cpp +++ b/dnfs2/src/dnfs2.cpp @@ -643,6 +643,7 @@ private: HttpClient http; http.set_max_response_size(kHttpMaxResponse); + http.set_timeout(static_cast(_cfg.gateway_timeout)); http.set_header("Content-Type: application/json"); http.set_header("Authorization: Bearer " + token); @@ -704,6 +705,7 @@ private: bool review_content(const std::string& content) { HttpClient http; http.set_max_response_size(kHttpMaxResponse); + http.set_timeout(static_cast(_cfg.review_timeout)); http.set_header("Content-Type: application/json"); json::object payload; diff --git a/docs/security/dnfs2_SECURITY_AUDIT_20260808.md b/docs/security/dnfs2_SECURITY_AUDIT_20260808.md index 22de903..c8f47b0 100644 --- a/docs/security/dnfs2_SECURITY_AUDIT_20260808.md +++ b/docs/security/dnfs2_SECURITY_AUDIT_20260808.md @@ -17,8 +17,8 @@ dnfs2 是 Python dnfs 的 C++20 重实现,基于 libhttp + libmsg + boost::jso | 类别 | 数量 | 状态 | |------|------|------| | 计划修复的 P0/P1(继承自 dnfs) | 6 | 全部有效 ✓ | -| 新发现问题 | 4 | 全部修复 ✓ | -| 已知限制(架构性/继承性) | 6 | 记录 | +| 新发现问题 | 5 | 全部修复 ✓ | +| 已知限制(架构性/继承性) | 5 | 记录 | --- @@ -130,6 +130,32 @@ if (!child_r.data.as_array().empty()) { if (cfg.threads < 1 || cfg.threads > 256) { ... exit(1); } ``` +### P2-N5: HttpClient 超时硬编码,配置值未生效 — ✓ 已修复 + +**位置**: `libhttp/src/HttpClient.cpp` L92-93(原)、`dnfs2/src/dnfs2.cpp` +**问题**: HttpClient `request_stream_sync` 硬编码 `CURLOPT_TIMEOUT=30L` / `CURLOPT_CONNECTTIMEOUT=10L`,无接口可覆盖。dnfs2 的 `review_timeout` 和 `gateway_timeout` 配置加载并校验范围后从未使用,实际超时恒为 30s。 + +**修复**: +1. libhttp `HttpClient` 新增 `set_timeout()` / `set_connect_timeout()` 接口(默认值 30s/10s,向后兼容 gateway/core) +2. `request_stream_sync` 使用成员变量替代硬编码值 +3. dnfs2 `decode_token()` 设置 `gateway_timeout`,`review_content()` 设置 `review_timeout` + +```cpp +// libhttp/HttpClient.hpp +void set_timeout(long seconds); +long get_timeout() const; +void set_connect_timeout(long seconds); +long get_connect_timeout() const; +long timeout_ = 30; // 默认 30s +long connect_timeout_ = 10; // 默认 10s + +// dnfs2 decode_token +http.set_timeout(static_cast(_cfg.gateway_timeout)); + +// dnfs2 review_content +http.set_timeout(static_cast(_cfg.review_timeout)); +``` + --- ## 4. 已知限制(不修复,架构性/继承性) @@ -142,10 +168,6 @@ update_note 允许将节点的 pid 改为任意已存在的节点,包括自身 delete_note 在"检查无子节点"和"执行删除"之间存在 TOCTOU 窗口,并发请求可在检查后添加子节点。修复需要 db2 支持事务或原子条件删除。dnfs2 已通过 `_call_db()` 统一检查 success 修复了"未检查 success"部分,但 TOCTOU 窗口仍存在。 -### L-3: 配置的超时时间未使用(P2) - -`review_timeout` 和 `gateway_timeout` 已加载并校验范围,但 HttpClient 硬编码 30s 超时(libhttp 限制,未提供 `set_timeout()` 接口)。实际超时为 30s 而非配置值。 - ### L-4: 无速率限制(P2,继承自 dnfs) 无请求频率限制,恶意用户可高频调用 add_note 填充数据库。应由 gateway/WAF 层防护。 @@ -191,18 +213,17 @@ dnfs 的错误消息泄露 IP 和用户名(如 `"未授权访问,从192.168. ## 6. 安全评级 -### 评级: A- +### 评级: A **理由**: - 计划修复的 6 项 P0/P1 全部有效 ✓ -- 新发现 4 项问题全部修复 ✓ (2 P1 + 2 P2) +- 新发现 5 项问题全部修复 ✓ (2 P1 + 3 P2) - IP/用户名泄露问题作为重写副效果已修复 ✓ -- 残余 6 项已知限制均为架构性/继承性问题,有明确修复路径 +- 残余 5 项已知限制均为架构性/继承性问题,有明确修复路径 - 相比 Python dnfs(评级 B,多个 P0/P1 未修复),安全基线显著提升 **扣分项**: - L-1/L-2 循环创建和 TOCTOU 未修复(需 db2 事务支持) -- L-3 超时配置未生效(需 libhttp 增强) --- @@ -215,5 +236,8 @@ dnfs 的错误消息泄露 IP 和用户名(如 `"未授权访问,从192.168. | dnfs2/src/dnfs2.cpp | P2-N3: delete_note 分离 db 错误和子节点检查 | | dnfs2/src/dnfs2.cpp | P2-N4: threads 上限 256 | | dnfs2/src/dnfs2.cpp | 新增 `#include ` | +| libhttp/inc/us/HttpClient.hpp | P2-N5: 新增 set_timeout/set_connect_timeout 接口 + 成员变量 | +| libhttp/src/HttpClient.cpp | P2-N5: 新增 set_timeout/set_connect_timeout 实现,request_stream_sync 使用成员变量 | +| dnfs2/src/dnfs2.cpp | P2-N5: decode_token 设置 gateway_timeout,review_content 设置 review_timeout | -编译验证: `g++ -std=c++20 -O3 -Wall -Werror` 通过,零错误零警告。 +编译验证: `g++ -std=c++20 -O3 -Wall -Werror` 通过,零错误零警告(libhttp + dnfs2 均重新编译)。 diff --git a/libhttp/inc/us/HttpClient.hpp b/libhttp/inc/us/HttpClient.hpp index d3da319..41464e3 100644 --- a/libhttp/inc/us/HttpClient.hpp +++ b/libhttp/inc/us/HttpClient.hpp @@ -65,6 +65,14 @@ public: void set_max_response_size(size_t bytes); size_t get_max_response_size() const; + // ===== 请求超时控制 ===== + // 设置整体请求超时(秒,含连接+传输)。默认 30s。 + void set_timeout(long seconds); + long get_timeout() const; + // 设置连接建立超时(秒)。默认 10s。 + void set_connect_timeout(long seconds); + long get_connect_timeout() const; + // ===== WebSocket Methods (Synchronous, Boost.Beast-based) ===== bool ws_connect(const std::string& url, int timeout_ms = 5000); bool ws_connect(const std::string& host, const std::string& port, @@ -84,6 +92,8 @@ private: std::map response_headers_; long response_code_ = 0; size_t max_response_size_ = 10 * 1024 * 1024; // 默认 10MB,对齐 WAF;0 表示不限制 + long timeout_ = 30; // 整体请求超时(秒),默认 30s + long connect_timeout_ = 10; // 连接建立超时(秒),默认 10s struct WsState { asio::io_context ioc; diff --git a/libhttp/src/HttpClient.cpp b/libhttp/src/HttpClient.cpp index 514a2a3..4174b69 100644 --- a/libhttp/src/HttpClient.cpp +++ b/libhttp/src/HttpClient.cpp @@ -42,6 +42,22 @@ size_t HttpClient::get_max_response_size() const { return max_response_size_; } +void HttpClient::set_timeout(long seconds) { + timeout_ = seconds; +} + +long HttpClient::get_timeout() const { + return timeout_; +} + +void HttpClient::set_connect_timeout(long seconds) { + connect_timeout_ = seconds; +} + +long HttpClient::get_connect_timeout() const { + return connect_timeout_; +} + void HttpClient::set_header(const std::string& header) { headers_ = curl_slist_append(headers_, header.c_str()); } @@ -89,8 +105,8 @@ void HttpClient::request_stream_sync( curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &write_ctx); - curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 30L); - curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 10L); + curl_easy_setopt(curl_, CURLOPT_TIMEOUT, timeout_); + curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, connect_timeout_); CURLcode res = curl_easy_perform(curl_);