feat: v3.0 Tool Calling — AI 本地文件操作系统
新增文件: - src/main/tool-security.ts: 路径白名单/黑名单、命令安全检查 - src/main/tool-handlers.ts: 7个工具实现(read/write/list/search/create/delete/run) - src/renderer/services/tool-registry.ts: 工具注册调度中心 - src/renderer/services/agent-engine.ts: Agent Loop 流式多轮工具调用引擎 - src/renderer/components/tool-confirm-modal.ts: 高风险操作确认对话框 修改文件: - types.d.ts: 新增 ToolCall/ToolResult/ToolCallRecord 等类型 - ollama.ts: chatStream 支持 tools 参数 - ipc.ts: 新增 tool:execute/getConfig/setAllowedDirs IPC - preload.ts: 暴露 tool API - chat-area.ts: 渲染工具调用卡片 - input-area.ts: 集成 Agent Loop 引擎 - settings-modal.ts: 工具调用开关设置 - index.html: 工具调用设置面板 + 确认对话框 HTML - style.css: 工具调用卡片、确认对话框样式 - main.ts: 初始化工具调用配置
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Tool Security - 安全检查模块
|
||||
* 路径白名单/黑名单、命令过滤、路径遍历检测
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
const HOME = os.homedir();
|
||||
|
||||
/** 默认允许的目录(可通过设置覆盖) */
|
||||
let allowedDirs: string[] = [
|
||||
HOME,
|
||||
path.join(HOME, 'Desktop'),
|
||||
path.join(HOME, 'Documents'),
|
||||
path.join(HOME, 'Downloads'),
|
||||
path.join(HOME, 'Projects'),
|
||||
path.join(HOME, 'projects'),
|
||||
'/tmp',
|
||||
];
|
||||
|
||||
/** 永久禁止的目录 */
|
||||
const BLOCKED_DIRS: string[] = [
|
||||
'/etc', '/sys', '/proc', '/dev', '/boot', '/root',
|
||||
'C:\\Windows', 'C:\\Program Files', 'C:\\ProgramData',
|
||||
path.join(HOME, '.ssh'),
|
||||
path.join(HOME, '.gnupg'),
|
||||
path.join(HOME, '.aws'),
|
||||
];
|
||||
|
||||
/** 命令黑名单 */
|
||||
const BLOCKED_COMMANDS: string[] = [
|
||||
'rm -rf /', 'rm -rf /*', ':(){ :|:& };:',
|
||||
'mkfs', 'dd if=', 'wipefs', 'shred',
|
||||
'shutdown', 'reboot', 'poweroff', 'halt',
|
||||
'useradd', 'usermod', 'userdel', 'passwd',
|
||||
'chmod 777', 'chown root',
|
||||
'crontab -e',
|
||||
'systemctl enable', 'systemctl disable',
|
||||
'curl | sh', 'wget | sh', 'curl | bash', 'wget | bash',
|
||||
'eval', 'exec >',
|
||||
];
|
||||
|
||||
export interface CheckResult {
|
||||
ok: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export function checkPathAllowed(targetPath: string, operation: 'read' | 'write'): CheckResult {
|
||||
const resolved = path.resolve(targetPath);
|
||||
|
||||
if (targetPath.split(path.sep).filter(s => s === '..').length > 5) {
|
||||
return { ok: false, reason: '路径遍历深度过大' };
|
||||
}
|
||||
|
||||
for (const blocked of BLOCKED_DIRS) {
|
||||
if (resolved === blocked || resolved.startsWith(blocked + path.sep)) {
|
||||
return { ok: false, reason: `禁止访问受保护路径: ${blocked}` };
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'write') {
|
||||
const inAllowedDir = allowedDirs.some(dir =>
|
||||
resolved === dir || resolved.startsWith(dir + path.sep)
|
||||
);
|
||||
if (!inAllowedDir) {
|
||||
return { ok: false, reason: `写操作被限制在允许的目录内。当前路径: ${resolved}` };
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function checkCommandAllowed(command: string): CheckResult {
|
||||
const lowerCmd = command.toLowerCase().trim();
|
||||
|
||||
for (const blocked of BLOCKED_COMMANDS) {
|
||||
if (lowerCmd.includes(blocked.toLowerCase())) {
|
||||
return { ok: false, reason: `命令包含被禁止的操作: ${blocked}` };
|
||||
}
|
||||
}
|
||||
|
||||
if (/(\||>|<)\s*(sh|bash|zsh|powershell|cmd)/i.test(lowerCmd)) {
|
||||
return { ok: false, reason: '禁止通过管道执行 shell 命令' };
|
||||
}
|
||||
|
||||
if (/\/dev\/tcp\//i.test(lowerCmd) || /bash\s+-i\s+>&/i.test(lowerCmd)) {
|
||||
return { ok: false, reason: '检测到疑似反弹 shell 操作' };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function setAllowedDirs(dirs: string[]): void {
|
||||
allowedDirs = dirs.map(d => path.resolve(d));
|
||||
}
|
||||
|
||||
export function getAllowedDirs(): string[] {
|
||||
return [...allowedDirs];
|
||||
}
|
||||
|
||||
export function getBlockedDirs(): string[] {
|
||||
return [...BLOCKED_DIRS];
|
||||
}
|
||||
Reference in New Issue
Block a user