流式渲染修复: - runId 机制防止 abort 后旧流事件污染新 run - run lock 防止并发 run 污染引擎状态 - abort race 提前退出工具执行等待 - TERMINATED 状态通过 stateChange 发射 - tool_call_delta 流式参数拼接 + pending 占位替换 - 首轮卡片创建路径统一,traceStep 按 ID 精确匹配 - compressed 事件转发为 toast 通知 安全增强: - ConfirmationHook 支持持久化自动执行(跨会话) - 设置面板新增自动执行工具管理 UI - SandboxManager 双重安全校验 fail-closed - 审计日志链式哈希防篡改 - PromptInjectionDefender 中文注入标记清理 - scanCode 28 模式 + base64/$() 检测 - validatePath realpathSync 防符号链接逃逸 - code-search 使用 execFile 防命令注入 新增工具: - file_editor、code_search、task_manager、diff_viewer 其他: - Agent Loop 加 PARSING/REFLECTING 状态 + 指数退避重试 - MemoryManager TF-IDF 语义检索 - run_command Windows 中文编码修复(chcp 65001) - 版本号 0.2.0 → 0.2.1
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
/**
|
|
* File Guard — 受保护文件守卫
|
|
*
|
|
* 确保工作空间根目录的 MEMORY.md 只能由系统内部(WorkspaceService)管理,
|
|
* 任何工具(read_file / write_file / search_files / run_command 等)均禁止直接读写。
|
|
*
|
|
* 注意:仅保护工作空间根目录的 MEMORY.md,
|
|
* 子目录或其他位置的同名文件不受限制。
|
|
*/
|
|
|
|
import { resolve, sep } from 'path';
|
|
|
|
/**
|
|
* 受保护文件名列表(工作空间根目录)
|
|
*/
|
|
const PROTECTED_FILES = ['MEMORY.md'];
|
|
|
|
/**
|
|
* 检查目标路径是否为工作空间根目录的受保护文件
|
|
*
|
|
* @param filePath 用户传入的文件路径(绝对或相对)
|
|
* @param workspacePath 当前工作空间根路径
|
|
* @returns true 如果路径指向受保护文件
|
|
*/
|
|
export function isProtectedWorkspaceFile(
|
|
filePath: string,
|
|
workspacePath: string,
|
|
): boolean {
|
|
const resolved = resolve(workspacePath, filePath);
|
|
const workspaceRoot = resolve(workspacePath);
|
|
|
|
for (const protectedName of PROTECTED_FILES) {
|
|
const protectedPath = resolve(workspaceRoot, protectedName);
|
|
if (resolved === protectedPath) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 检查路径是否在工作空间内(防止路径遍历攻击)
|
|
*
|
|
* 修复前缀碰撞漏洞:`/home/user/app-evil` 不应被误判为在 `/home/user/app` 内。
|
|
*
|
|
* @param filePath 用户传入的文件路径
|
|
* @param workspacePath 当前工作空间根路径
|
|
* @returns true 如果路径在工作空间内
|
|
*/
|
|
export function isPathWithinWorkspace(
|
|
filePath: string,
|
|
workspacePath: string,
|
|
): boolean {
|
|
const resolved = resolve(workspacePath, filePath);
|
|
const workspaceRoot = resolve(workspacePath);
|
|
return resolved === workspaceRoot || resolved.startsWith(workspaceRoot + sep);
|
|
}
|
|
|
|
/**
|
|
* 检查命令字符串是否尝试访问工作空间根目录的受保护文件
|
|
*
|
|
* 用于 run_command 工具的命令校验。
|
|
* 仅匹配直接引用的 MEMORY.md(前面是命令起始/空白/引号/分号/管道),
|
|
* 不拦截子目录路径中的同名文件(如 subdir/MEMORY.md 或 subdir\MEMORY.md)。
|
|
*
|
|
* 注意:run_command 的工作目录固定为 workspacePath,因此裸引用 MEMORY.md
|
|
* 等价于工作空间根目录的 MEMORY.md。
|
|
*
|
|
* @param command Shell 命令字符串
|
|
* @returns true 如果命令直接引用了受保护文件名
|
|
*/
|
|
export function commandTouchesProtectedFile(command: string): boolean {
|
|
const lowerCmd = command.toLowerCase();
|
|
for (const protectedName of PROTECTED_FILES) {
|
|
const lowerName = protectedName.toLowerCase();
|
|
// 前面是起始/空白/引号/分号/管道/&/>;后面是结束/空白/引号/分号/管道/&/</>
|
|
// 这样 subdir/MEMORY.md 和 subdir\MEMORY.md 不会被匹配(前面是 / 或 \)
|
|
const escaped = lowerName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const regex = new RegExp(`(?:^|[\\s"'|;&>])${escaped}(?:$|[\\s"'|;&<])`, 'i');
|
|
if (regex.test(lowerCmd)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|