fix: 修复 SSRF 重定向绕过 + 符号链接路径遍历 + TodoItemSummary 字段缺失
P0: src/tools/http_request.rs SSRF 重定向绕过 - reqwest::Client 默认跟随最多 10 次重定向,is_private_host 仅检查初始 URL - 攻击者可用公网 URL 返回 302 → http://127.0.0.1/ 或 http://169.254.169.254/(云元数据端点)绕过防护访问内网 - 修复:.redirect(reqwest::redirect::Policy::none()) 完全禁用重定向 P1: src/tools/file_read/write/edit.rs 符号链接路径遍历 - resolve_path 用 starts_with 检查但未 canonicalize - 攻击者可在 allowed_dir 内创建指向 /etc/passwd 的符号链接绕过限制 - 修复:对 resolved 和 allowed 均执行 canonicalize 后比较 - file_read: 文件必须存在,canonicalize 失败直接报错 - file_write/edit: 文件可能不存在,降级到父目录 canonicalize P1: src/protocol/mod.rs + list_todos.rs TodoItemSummary 字段缺失 - 后端 TodoItemSummary 仅返回 4 字段,前端期望 7 字段 - 缺失 priority, created_at, updated_at,前端 TodoPanel 无法显示 优先级和时间戳 - 修复:struct 补齐 3 字段,list_todos 构造时传递完整字段
This commit is contained in:
parent
53a45ad7c4
commit
58f461c953
@ -76,6 +76,9 @@ impl CommandHandler for ListTodosCommandHandler {
|
||||
id: r.id,
|
||||
content: r.content,
|
||||
status: r.status,
|
||||
priority: r.priority,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
created_by_message_id: r.created_by_message_id,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -88,6 +88,9 @@ pub struct TodoItemSummary {
|
||||
pub id: String,
|
||||
pub content: String,
|
||||
pub status: String,
|
||||
pub priority: String,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub created_by_message_id: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@ -33,11 +33,31 @@ impl FileEditTool {
|
||||
|
||||
// Check directory restriction
|
||||
if let Some(ref allowed) = self.allowed_dir {
|
||||
let allowed_path = Path::new(allowed);
|
||||
if !resolved.starts_with(allowed_path) {
|
||||
// canonicalize both paths to resolve symlinks and prevent path traversal
|
||||
// via symlinks inside allowed_dir pointing outside.
|
||||
// For edit tool the target file may not exist yet; fall back to
|
||||
// canonicalizing the parent directory.
|
||||
let canonical_allowed = std::fs::canonicalize(allowed)
|
||||
.map_err(|e| format!("Failed to canonicalize allowed dir '{}': {}", allowed, e))?;
|
||||
let canonical_resolved = match std::fs::canonicalize(&resolved) {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
// File doesn't exist yet; canonicalize parent directory
|
||||
let parent = resolved.parent().ok_or_else(|| {
|
||||
format!("Path '{}' has no parent directory", path)
|
||||
})?;
|
||||
let canonical_parent = std::fs::canonicalize(parent).map_err(|e| {
|
||||
format!("Failed to canonicalize parent directory of '{}': {}", path, e)
|
||||
})?;
|
||||
canonical_parent.join(resolved.file_name().ok_or_else(|| {
|
||||
format!("Path '{}' has no file name component", path)
|
||||
})?)
|
||||
}
|
||||
};
|
||||
if !canonical_resolved.starts_with(&canonical_allowed) {
|
||||
return Err(format!(
|
||||
"Path '{}' is outside allowed directory '{}'",
|
||||
path, allowed
|
||||
"Path '{}' (resolves to '{}') is outside allowed directory '{}'",
|
||||
path, canonical_resolved.display(), canonical_allowed.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,11 +37,18 @@ impl FileReadTool {
|
||||
|
||||
// Check directory restriction
|
||||
if let Some(ref allowed) = self.allowed_dir {
|
||||
let allowed_path = Path::new(allowed);
|
||||
if !resolved.starts_with(allowed_path) {
|
||||
// canonicalize both paths to resolve symlinks and prevent path traversal
|
||||
// via symlinks inside allowed_dir pointing outside
|
||||
let canonical_allowed = std::fs::canonicalize(allowed)
|
||||
.map_err(|e| format!("Failed to canonicalize allowed dir '{}': {}", allowed, e))?;
|
||||
// For read tool, file must exist; canonicalize will fail for non-existent paths
|
||||
// which is acceptable (returns error)
|
||||
let canonical_resolved = std::fs::canonicalize(&resolved)
|
||||
.map_err(|e| format!("Failed to canonicalize path '{}': {}", path, e))?;
|
||||
if !canonical_resolved.starts_with(&canonical_allowed) {
|
||||
return Err(format!(
|
||||
"Path '{}' is outside allowed directory '{}'",
|
||||
path, allowed
|
||||
"Path '{}' (resolves to '{}') is outside allowed directory '{}'",
|
||||
path, canonical_resolved.display(), canonical_allowed.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,11 +32,31 @@ impl FileWriteTool {
|
||||
|
||||
// Check directory restriction
|
||||
if let Some(ref allowed) = self.allowed_dir {
|
||||
let allowed_path = Path::new(allowed);
|
||||
if !resolved.starts_with(allowed_path) {
|
||||
// canonicalize both paths to resolve symlinks and prevent path traversal
|
||||
// via symlinks inside allowed_dir pointing outside.
|
||||
// For write tool the target file may not exist yet; fall back to
|
||||
// canonicalizing the parent directory.
|
||||
let canonical_allowed = std::fs::canonicalize(allowed)
|
||||
.map_err(|e| format!("Failed to canonicalize allowed dir '{}': {}", allowed, e))?;
|
||||
let canonical_resolved = match std::fs::canonicalize(&resolved) {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
// File doesn't exist yet; canonicalize parent directory
|
||||
let parent = resolved.parent().ok_or_else(|| {
|
||||
format!("Path '{}' has no parent directory", path)
|
||||
})?;
|
||||
let canonical_parent = std::fs::canonicalize(parent).map_err(|e| {
|
||||
format!("Failed to canonicalize parent directory of '{}': {}", path, e)
|
||||
})?;
|
||||
canonical_parent.join(resolved.file_name().ok_or_else(|| {
|
||||
format!("Path '{}' has no file name component", path)
|
||||
})?)
|
||||
}
|
||||
};
|
||||
if !canonical_resolved.starts_with(&canonical_allowed) {
|
||||
return Err(format!(
|
||||
"Path '{}' is outside allowed directory '{}'",
|
||||
path, allowed
|
||||
"Path '{}' (resolves to '{}') is outside allowed directory '{}'",
|
||||
path, canonical_resolved.display(), canonical_allowed.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,6 +311,7 @@ impl Tool for HttpRequestTool {
|
||||
|
||||
let client = match reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(self.timeout_secs))
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user