fix: 代码审查问题修复
- C1 (Critical): 添加 PyYAML 解析 frontmatter,支持多行值和列表 - M1 (Major): 添加 FTS5 查询验证,防止注入攻击 - M2/M3 (Major): 工具类使用显式 wiki_vault_path 参数 - M4 (Major): 集成 aiofiles 实现真正的异步文件 I/O - M5 (Major): 改用 logger.exception() 记录完整堆栈 Co-Authored-By: Claude Dev <noreply@anthropic.com>
This commit is contained in:
@@ -272,13 +272,68 @@ class Database:
|
||||
|
||||
# === FTS5 搜索 ===
|
||||
|
||||
def _validate_fts_query(self, query: str) -> str:
|
||||
"""
|
||||
验证和清理 FTS5 查询字符串,防止注入攻击
|
||||
|
||||
FTS5 支持的特殊字符:
|
||||
- 双引号:短语查询
|
||||
- *:前缀查询
|
||||
- AND, OR, NOT:布尔运算符
|
||||
|
||||
验证规则:
|
||||
- 移除不安全的控制字符
|
||||
- 限制查询长度(防止 DoS)
|
||||
- 转义双引号防止短语注入
|
||||
"""
|
||||
if not query:
|
||||
raise ValueError("Query cannot be empty")
|
||||
|
||||
# 1. 限制查询长度
|
||||
max_query_length = 500
|
||||
if len(query) > max_query_length:
|
||||
logger.warning(f"Query too long ({len(query)} chars), truncating to {max_query_length}")
|
||||
query = query[:max_query_length]
|
||||
|
||||
# 2. 移除控制字符(除了换行、制表符)
|
||||
import re
|
||||
query = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]', '', query)
|
||||
|
||||
# 3. 转义未闭合的双引号(防止短语查询注入)
|
||||
# 计算引号数量,如果是奇数则转义最后一个
|
||||
quote_count = query.count('"')
|
||||
if quote_count % 2 != 0:
|
||||
# 找到最后一个引号并转义
|
||||
last_quote_idx = query.rfind('"')
|
||||
query = query[:last_quote_idx] + '\\"' + query[last_quote_idx+1:]
|
||||
|
||||
# 4. 防止布尔运算符注入(移除前后空格的运算符)
|
||||
# 这是为了防止类似 "term AND DROP TABLE" 的攻击
|
||||
# FTS5 会在查询语法错误时返回空结果,但我们需要额外保护
|
||||
query = re.sub(r'\s+(AND|OR|NOT)\s+', ' ', query, flags=re.IGNORECASE)
|
||||
|
||||
return query.strip()
|
||||
|
||||
async def fts_search(self, query: str, limit: int = 10) -> List[WikiPage]:
|
||||
"""FTS5 全文搜索"""
|
||||
# 验证和清理查询
|
||||
try:
|
||||
safe_query = self._validate_fts_query(query)
|
||||
except ValueError as e:
|
||||
logger.warning(f"Invalid FTS query: {e}")
|
||||
return []
|
||||
|
||||
# 先从 FTS5 获取匹配的路径
|
||||
fts_rows = await self.fetch_all(
|
||||
"SELECT path FROM wiki_fts WHERE wiki_fts MATCH ? ORDER BY rank LIMIT ?",
|
||||
(query, limit)
|
||||
)
|
||||
try:
|
||||
fts_rows = await self.fetch_all(
|
||||
"SELECT path FROM wiki_fts WHERE wiki_fts MATCH ? ORDER BY rank LIMIT ?",
|
||||
(safe_query, limit)
|
||||
)
|
||||
except aiosqlite.OperationalError as e:
|
||||
# FTS5 语法错误时返回空结果(不应该崩溃)
|
||||
logger.warning(f"FTS5 query failed: {e}")
|
||||
return []
|
||||
|
||||
paths = [row[0] for row in fts_rows]
|
||||
|
||||
if not paths:
|
||||
|
||||
Reference in New Issue
Block a user