5e32375f0e
架构变更: - 采用 pnpm workspaces 实现 Monorepo 结构 - 将现有代码迁移到 packages/core - 新增 packages/server HTTP 服务层 Server 功能: - REST API: 会话管理、工具管理、配置管理 - WebSocket: 实时双向通信支持 - SSE: 服务端事件推送 - Hono + Bun 作为运行时 API 端点: - GET/POST /api/sessions - 会话 CRUD - GET/POST /api/sessions/:id/messages - 消息管理 - GET /api/sessions/:id/events - SSE 事件流 - WS /api/ws/:sessionId - WebSocket 连接 - GET/POST /api/tools - 工具管理 - GET/PUT /api/config - 配置管理
235 lines
7.8 KiB
TypeScript
235 lines
7.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
mergePermissions,
|
|
matchRule,
|
|
checkBashPermission,
|
|
checkFilePathPermission,
|
|
SYSTEM_DEFAULT_PERMISSION,
|
|
} from '../../../src/agent/permission-merger.js';
|
|
import type { AgentPermission, AgentBashPermission } from '../../../src/agent/types.js';
|
|
|
|
describe('matchRule - 命令规则匹配', () => {
|
|
describe('通配符 * 匹配', () => {
|
|
it('git diff* 匹配 git diff --staged', () => {
|
|
expect(matchRule('git diff --staged', 'git diff*')).toBe(true);
|
|
});
|
|
|
|
it('git diff* 不匹配 git status', () => {
|
|
expect(matchRule('git status', 'git diff*')).toBe(false);
|
|
});
|
|
|
|
it('rm -rf* 匹配危险命令', () => {
|
|
expect(matchRule('rm -rf /', 'rm -rf*')).toBe(true);
|
|
expect(matchRule('rm -rf /home/user', 'rm -rf*')).toBe(true);
|
|
});
|
|
|
|
it('rm -rf* 不匹配普通 rm', () => {
|
|
expect(matchRule('rm file.txt', 'rm -rf*')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('精确匹配', () => {
|
|
it('pwd 精确匹配', () => {
|
|
expect(matchRule('pwd', 'pwd')).toBe(true);
|
|
});
|
|
|
|
it('pwd 不匹配带参数', () => {
|
|
expect(matchRule('pwd /home', 'pwd')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('大小写不敏感', () => {
|
|
it('忽略大小写', () => {
|
|
expect(matchRule('GIT DIFF', 'git diff*')).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('checkBashPermission - Bash 权限检查', () => {
|
|
it('禁用时返回 deny', () => {
|
|
const permission: AgentBashPermission = { enabled: false };
|
|
expect(checkBashPermission('ls', permission)).toBe('deny');
|
|
});
|
|
|
|
it('匹配 allow 规则', () => {
|
|
const permission: AgentBashPermission = {
|
|
enabled: true,
|
|
rules: [{ pattern: 'ls *', action: 'allow' }],
|
|
default: 'deny',
|
|
};
|
|
expect(checkBashPermission('ls -la', permission)).toBe('allow');
|
|
});
|
|
|
|
it('匹配 deny 规则', () => {
|
|
const permission: AgentBashPermission = {
|
|
enabled: true,
|
|
rules: [{ pattern: 'rm -rf*', action: 'deny' }],
|
|
default: 'allow',
|
|
};
|
|
expect(checkBashPermission('rm -rf /', permission)).toBe('deny');
|
|
});
|
|
|
|
it('无匹配时返回默认值', () => {
|
|
const permission: AgentBashPermission = {
|
|
enabled: true,
|
|
rules: [],
|
|
default: 'ask',
|
|
};
|
|
expect(checkBashPermission('npm install', permission)).toBe('ask');
|
|
});
|
|
|
|
it('规则优先级:先匹配的规则优先', () => {
|
|
const permission: AgentBashPermission = {
|
|
enabled: true,
|
|
rules: [
|
|
{ pattern: 'git push --force*', action: 'deny' },
|
|
{ pattern: 'git push*', action: 'ask' },
|
|
],
|
|
default: 'allow',
|
|
};
|
|
expect(checkBashPermission('git push --force origin', permission)).toBe('deny');
|
|
expect(checkBashPermission('git push origin', permission)).toBe('ask');
|
|
});
|
|
});
|
|
|
|
describe('checkFilePathPermission - 文件路径权限检查', () => {
|
|
it('无敏感路径规则返回 null', () => {
|
|
expect(checkFilePathPermission('/home/user/file.txt', undefined)).toBeNull();
|
|
expect(checkFilePathPermission('/home/user/file.txt', [])).toBeNull();
|
|
});
|
|
|
|
it('匹配敏感路径规则', () => {
|
|
const rules = [
|
|
{ pattern: '*.env', action: 'deny' as const },
|
|
{ pattern: '/etc/*', action: 'ask' as const },
|
|
];
|
|
expect(checkFilePathPermission('.env', rules)).toBe('deny');
|
|
expect(checkFilePathPermission('/etc/passwd', rules)).toBe('ask');
|
|
});
|
|
|
|
it('不匹配时返回 null', () => {
|
|
const rules = [{ pattern: '*.env', action: 'deny' as const }];
|
|
expect(checkFilePathPermission('config.json', rules)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('mergePermissions - 权限合并', () => {
|
|
describe('优先级:Agent > Global > System', () => {
|
|
it('Agent 配置覆盖 Global 和 System', () => {
|
|
const system: AgentPermission = { file: { read: 'allow', write: 'ask' } };
|
|
const global: AgentPermission = { file: { write: 'allow' } };
|
|
const agent: AgentPermission = { file: { write: 'deny' } };
|
|
|
|
const merged = mergePermissions(system, global, agent);
|
|
expect(merged.file?.write).toBe('deny');
|
|
});
|
|
|
|
it('Global 配置覆盖 System', () => {
|
|
const system: AgentPermission = { file: { write: 'ask' } };
|
|
const global: AgentPermission = { file: { write: 'allow' } };
|
|
|
|
const merged = mergePermissions(system, global, undefined);
|
|
expect(merged.file?.write).toBe('allow');
|
|
});
|
|
|
|
it('无覆盖时使用 System 默认值', () => {
|
|
const merged = mergePermissions(SYSTEM_DEFAULT_PERMISSION, undefined, undefined);
|
|
expect(merged.file?.read).toBe('allow');
|
|
expect(merged.file?.write).toBe('ask');
|
|
});
|
|
});
|
|
|
|
describe('Bash 权限合并', () => {
|
|
it('Agent 禁用 bash 覆盖全局', () => {
|
|
const system: AgentPermission = { bash: { enabled: true } };
|
|
const global: AgentPermission = { bash: { enabled: true } };
|
|
const agent: AgentPermission = { bash: { enabled: false } };
|
|
|
|
const merged = mergePermissions(system, global, agent);
|
|
expect(merged.bash?.enabled).toBe(false);
|
|
});
|
|
|
|
it('Global 禁用 bash 且 Agent 未覆盖', () => {
|
|
const system: AgentPermission = { bash: { enabled: true } };
|
|
const global: AgentPermission = { bash: { enabled: false } };
|
|
|
|
const merged = mergePermissions(system, global, undefined);
|
|
expect(merged.bash?.enabled).toBe(false);
|
|
});
|
|
|
|
it('规则按优先级合并:Agent > Global > System', () => {
|
|
const system: AgentPermission = {
|
|
bash: { rules: [{ pattern: 'ls *', action: 'allow' }] },
|
|
};
|
|
const global: AgentPermission = {
|
|
bash: { rules: [{ pattern: 'cat *', action: 'allow' }] },
|
|
};
|
|
const agent: AgentPermission = {
|
|
bash: { rules: [{ pattern: 'rm *', action: 'deny' }] },
|
|
};
|
|
|
|
const merged = mergePermissions(system, global, agent);
|
|
// Agent 规则在前
|
|
expect(merged.bash?.rules?.[0].pattern).toBe('rm *');
|
|
expect(merged.bash?.rules?.[1].pattern).toBe('cat *');
|
|
expect(merged.bash?.rules?.[2].pattern).toBe('ls *');
|
|
});
|
|
});
|
|
|
|
describe('Git 权限合并', () => {
|
|
it('合并所有级别的 Git 权限', () => {
|
|
const system: AgentPermission = { git: { read: 'allow', write: 'ask', dangerous: 'deny' } };
|
|
const agent: AgentPermission = { git: { write: 'deny' } };
|
|
|
|
const merged = mergePermissions(system, undefined, agent);
|
|
expect(merged.git?.read).toBe('allow'); // 来自 system
|
|
expect(merged.git?.write).toBe('deny'); // 来自 agent
|
|
expect(merged.git?.dangerous).toBe('deny'); // 来自 system
|
|
});
|
|
});
|
|
|
|
describe('Web 权限合并', () => {
|
|
it('合并 Web 权限', () => {
|
|
const system: AgentPermission = { web: 'ask' };
|
|
const agent: AgentPermission = { web: 'deny' };
|
|
|
|
const merged = mergePermissions(system, undefined, agent);
|
|
expect(merged.web).toBe('deny');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('SYSTEM_DEFAULT_PERMISSION - 系统默认权限', () => {
|
|
it('文件读取默认允许', () => {
|
|
expect(SYSTEM_DEFAULT_PERMISSION.file?.read).toBe('allow');
|
|
});
|
|
|
|
it('文件写入默认询问', () => {
|
|
expect(SYSTEM_DEFAULT_PERMISSION.file?.write).toBe('ask');
|
|
});
|
|
|
|
it('Bash 默认启用', () => {
|
|
expect(SYSTEM_DEFAULT_PERMISSION.bash?.enabled).toBe(true);
|
|
});
|
|
|
|
it('包含安全命令白名单', () => {
|
|
const rules = SYSTEM_DEFAULT_PERMISSION.bash?.rules ?? [];
|
|
const lsRule = rules.find((r) => r.pattern === 'ls *');
|
|
expect(lsRule?.action).toBe('allow');
|
|
});
|
|
|
|
it('包含危险命令黑名单', () => {
|
|
const rules = SYSTEM_DEFAULT_PERMISSION.bash?.rules ?? [];
|
|
const rmRule = rules.find((r) => r.pattern === 'rm -rf *');
|
|
expect(rmRule?.action).toBe('deny');
|
|
});
|
|
|
|
it('Git 读取默认允许', () => {
|
|
expect(SYSTEM_DEFAULT_PERMISSION.git?.read).toBe('allow');
|
|
});
|
|
|
|
it('Git 危险操作默认拒绝', () => {
|
|
expect(SYSTEM_DEFAULT_PERMISSION.git?.dangerous).toBe('deny');
|
|
});
|
|
});
|