feat: 升级至 v0.3.3 — 新增 delete_file 工具 + 文件工具全面优化

## 主要变更

### 1. 新增 delete_file 工具(26 → 27 个)
- 支持:删除文件/空目录(默认)/递归删除非空目录(recursive: true)
- 5 层安全防护:
  * isPathWithinWorkspace — 路径遍历防护
  * isProtectedWorkspaceFile — MEMORY.md 拦截
  * 工作空间根目录保护 — 禁止删除 workspace 本身
  * 递归删除需显式开启 — 默认仅删空目录
  * riskLevel: HIGH + requiresPermission — 破坏性操作必须确认
- 友好错误处理:ENOTEMPTY 时提示设置 recursive: true

### 2. 文件工具全面优化(6 个工具)
- 抽取共享代码到 file-guard.ts:
  * safeResolvePath — 合并路径遍历 + MEMORY.md 校验
  * matchGlob — 简易通配符匹配
  * extractErrorMessage — 统一错误提取
  * MAX_FILE_SIZE_BYTES (10MB) / FILE_TOOL_TIMEOUT_MS (15s) / MAX_LINE_LENGTH (10000)
- read_file:stat 预检 + 超长行截断 + 二进制检测 + 大小上限
- write_file:原子写入(临时文件+rename)+ 内容大小上限 + append 返回 new_file_size
- list_directory:1000 结果上限 + modified time + include_hidden 参数 + 提前终止优化
- search_files:regex lastIndex 修复 + context_lines + include_hidden + 大文件跳过
- file_editor:dry_run 预览模式 + 文件大小上限

### 3. 审计修复(1 FAIL + 3 WARN)
- FAIL: isBinaryFile 用 bytesRead 限制循环,修复 < 8KB 文本误判为二进制
- WARN-1: file-editor dry_run preview 分模式计算,修复 insert 范围过大
- WARN-2: write_file 允许空字符串创建空文件
- WARN-3: list_directory listDir 提前终止,避免大目录全量遍历
This commit is contained in:
thzxx
2026-07-14 21:42:46 +08:00
parent 0fc589e73b
commit 4554177db0
8 changed files with 553 additions and 140 deletions
@@ -106,3 +106,74 @@ export function commandTouchesProtectedFile(command: string): boolean {
}
return false;
}
// ===== 共享工具函数(v0.3.2 抽取,消除 filesystem.ts 与 file-editor.ts 的重复)=====
/**
* 共享路径解析 + 安全校验
*
* 合并两层安全检查:
* 1. isPathWithinWorkspace — 路径遍历防护(含符号链接 realpathSync 二次校验)
* 2. isProtectedWorkspaceFile — MEMORY.md 拦截
*
* @param filePath 用户传入的文件路径(绝对或相对)
* @param workspacePath 当前工作空间根路径
* @returns 解析后的绝对路径
* @throws Error 路径越界或访问受保护文件时抛出
*/
export function safeResolvePath(filePath: string, workspacePath: string): string {
const resolved = resolve(workspacePath, filePath);
// 安全检查:路径遍历防护(修复前缀碰撞漏洞)
if (!isPathWithinWorkspace(filePath, workspacePath)) {
throw new Error(`Path traversal detected: ${filePath}`);
}
// 受保护文件检查:MEMORY.md 仅由系统内部管理
if (isProtectedWorkspaceFile(filePath, workspacePath)) {
throw new Error('Access denied: MEMORY.md is managed by the memory system and cannot be accessed via file tools');
}
return resolved;
}
/**
* 共享 glob 匹配(简易通配符 → 正则)
*
* 支持 `*`(任意字符序列)和 `?`(单字符),大小写不敏感。
* 其他正则元字符会被转义。
*
* @param name 待匹配的文件名
* @param glob 通配符模式(如 "*.ts"、"test?.js"
* @returns 是否匹配
*/
export function matchGlob(name: string, glob: string): boolean {
const pattern = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
return new RegExp(`^${pattern}$`, 'i').test(name);
}
/**
* 共享错误提取
*
* 统一从 unknown 错误对象中提取 message 字符串
*
* @param error catch 块中的 unknown 错误
* @returns 错误消息字符串
*/
export function extractErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
/**
* 共享文件大小限制常量
*
* read_file/write_file/file_editor 共用,防止 OOM
*/
export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
/**
* 共享超时常量(v0.3.2 统一文件工具 timeoutMs
*/
export const FILE_TOOL_TIMEOUT_MS = 15_000;
/**
* 共享单行最大长度(防止超长行爆 token)
*/
export const MAX_LINE_LENGTH = 10_000;