feat: 添加权限管理系统
- 实现 tree-sitter 解析 bash 命令,准确识别管道、&&、子shell 等复杂命令 - 新增权限检查器模式,支持 allow/deny/ask 三级权限控制 - BashPermissionChecker: 支持命令模式匹配和外部目录访问检测 - FilePermissionChecker: 支持文件操作分级(read/write/edit/list/search/delete) - 敏感路径规则:系统目录拒绝,SSH/AWS 等凭证目录需确认 - 会话级权限记忆,用户决定可在当前会话内生效 - 所有工具(bash、read_file、write_file、edit_file、list_directory、search_files)已集成权限检查
This commit is contained in:
+18
-11
@@ -2,6 +2,7 @@ import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -16,7 +17,7 @@ export const bashTool: Tool = {
|
||||
},
|
||||
cwd: {
|
||||
type: 'string',
|
||||
description: '工作目录(可选)',
|
||||
description: '工作目录(可选,默认为当前目录)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
@@ -24,22 +25,28 @@ export const bashTool: Tool = {
|
||||
const command = params.command as string;
|
||||
const cwd = (params.cwd as string) || process.cwd();
|
||||
|
||||
// 安全检查:禁止危险命令
|
||||
const dangerousPatterns = [
|
||||
/rm\s+-rf\s+\//, // rm -rf /
|
||||
/mkfs/, // 格式化磁盘
|
||||
/dd\s+if=.*of=\/dev/, // 直接写入设备
|
||||
/>\s*\/dev\/sd/, // 重定向到磁盘设备
|
||||
];
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkBashPermission({
|
||||
command,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(command)) {
|
||||
if (!permResult.allowed) {
|
||||
// 如果需要用户确认但没有设置回调,返回等待确认的状态
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: '检测到危险命令,已阻止执行',
|
||||
error: `需要用户确认: ${command}\n原因: ${permResult.reason || '需要权限确认'}\n模式: ${permResult.patterns?.join(', ') || ''}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '命令不被允许执行'}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+26
-1
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
export const editFileTool: Tool = {
|
||||
name: 'edit_file',
|
||||
@@ -27,9 +28,33 @@ export const editFileTool: Tool = {
|
||||
const filePath = params.path as string;
|
||||
const oldString = params.old_string as string;
|
||||
const newString = params.new_string as string;
|
||||
const cwd = process.cwd();
|
||||
const absolutePath = path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: path.join(process.cwd(), filePath);
|
||||
: path.join(cwd, filePath);
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkFilePermission({
|
||||
operation: 'edit',
|
||||
path: absolutePath,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认: 编辑 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '不允许编辑此文件'}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(absolutePath, 'utf-8');
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
export const listDirTool: Tool = {
|
||||
name: 'list_directory',
|
||||
@@ -15,9 +16,33 @@ export const listDirTool: Tool = {
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const dirPath = params.path as string;
|
||||
const cwd = process.cwd();
|
||||
const absolutePath = path.isAbsolute(dirPath)
|
||||
? dirPath
|
||||
: path.join(process.cwd(), dirPath);
|
||||
: path.join(cwd, dirPath);
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkFilePermission({
|
||||
operation: 'list',
|
||||
path: absolutePath,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认: 列出目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '不允许列出此目录'}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(absolutePath, { withFileTypes: true });
|
||||
|
||||
+26
-1
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
export const readFileTool: Tool = {
|
||||
name: 'read_file',
|
||||
@@ -15,9 +16,33 @@ export const readFileTool: Tool = {
|
||||
},
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const filePath = params.path as string;
|
||||
const cwd = process.cwd();
|
||||
const absolutePath = path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: path.join(process.cwd(), filePath);
|
||||
: path.join(cwd, filePath);
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkFilePermission({
|
||||
operation: 'read',
|
||||
path: absolutePath,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认: 读取 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '不允许读取此文件'}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(absolutePath, 'utf-8');
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
export const searchFilesTool: Tool = {
|
||||
name: 'search_files',
|
||||
@@ -21,9 +22,33 @@ export const searchFilesTool: Tool = {
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const directory = params.directory as string;
|
||||
const pattern = params.pattern as string;
|
||||
const cwd = process.cwd();
|
||||
const absolutePath = path.isAbsolute(directory)
|
||||
? directory
|
||||
: path.join(process.cwd(), directory);
|
||||
: path.join(cwd, directory);
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkFilePermission({
|
||||
operation: 'search',
|
||||
path: absolutePath,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认: 搜索目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '不允许搜索此目录'}`,
|
||||
};
|
||||
}
|
||||
|
||||
const matches: string[] = [];
|
||||
const regex = new RegExp(
|
||||
|
||||
+26
-1
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import type { Tool, ToolResult } from '../types/index.js';
|
||||
import { loadDescription } from './load_description.js';
|
||||
import { getPermissionManager } from '../permission/index.js';
|
||||
|
||||
export const writeFileTool: Tool = {
|
||||
name: 'write_file',
|
||||
@@ -21,9 +22,33 @@ export const writeFileTool: Tool = {
|
||||
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||
const filePath = params.path as string;
|
||||
const content = params.content as string;
|
||||
const cwd = process.cwd();
|
||||
const absolutePath = path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: path.join(process.cwd(), filePath);
|
||||
: path.join(cwd, filePath);
|
||||
|
||||
// 权限检查
|
||||
const permissionManager = getPermissionManager();
|
||||
const permResult = await permissionManager.checkFilePermission({
|
||||
operation: 'write',
|
||||
path: absolutePath,
|
||||
workdir: cwd,
|
||||
});
|
||||
|
||||
if (!permResult.allowed) {
|
||||
if (permResult.needsConfirmation) {
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `需要用户确认: 写入 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `权限被拒绝: ${permResult.reason || '不允许写入此文件'}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user