feat: 升级至 v0.3.0 — 安全增强、死循环检测、六轮全面审计修复
大版本迭代,新增安全增强、Agent Loop 增强、UI/UX 增强,经六轮全面审计修复所有问题。
新增功能:
- OutputValidator 事实一致性检查 + 幻觉检测
- PromptInjectionDefender 语义级检测(指令性动词密度、角色边界、分隔符嵌套)
- DeadLoopError 死循环检测(连续3轮相同工具调用自动终止)
- PolicyEngine maxFrequency 滑动窗口频率限制
- MemoryManager TF-IDF 语义检索 + IDF 缓存原子替换
- 斜杠命令(/tool /memory /clear /export)+ 快捷键(Ctrl+B/J/Shift+F/N/[/])
- 专注模式(Ctrl+Shift+F)带面板状态快照保存/恢复
六轮审计修复(共修复 3 CRITICAL + 8 HIGH + 13 MEDIUM + 11 LOW):
CRITICAL:
- main.ts 传入 createAdapter 而非 reloadAdapter,导致 Provider 切换完全失效
- Ctrl+N/Ctrl+[/Ctrl+] 不同步 agent-store,导致消息发到错误会话
- engine.ts emit('error') 无监听器导致 DONE 事件丢失、前端卡死
HIGH:
- 死循环检测在工具执行之后(移入 PARSING 后 EXECUTING 前)
- deadLoop 事件前端未处理
- ConfirmationHook.clearPending 从未调用导致定时器泄漏
- engine.ts retry abort listener 未移除导致监听器堆积
- MCPManager JSON.parse 无 try-catch 导致初始化崩溃
- sendMessage 自动创建会话不同步 session-store
- setCurrentSession 竞态导致旧请求覆盖新会话数据
MEDIUM:
- toggleFocusMode 覆盖用户原有面板状态
- 正则检测可被常见词绕过
- 频率限制内存泄漏 + customPolicies 覆盖
- LIKE 回退转义未包含反斜杠
- OutputValidator 新功能未传入 toolResults/context
- MemoryConsolidator LLM 调用无超时保护
- AuditService 每次 log 都查询数据库
- browser-window-manager 超时后未停止页面加载
- output-validator URL 比较大小写敏感
- FileReader 无 onerror 导致 Promise 永久挂起
- /clear /export 不关闭斜杠菜单
- 专注模式下 toggleSidebar/toggleDetail 未恢复另一面板快照
LOW:
- abortPromise 事件监听器堆积
- RateLimitHook Map 内存泄漏
- memory.ts await 同步方法
- PolicyEngine 死代码清理
- Orchestrator sessionDepth 会话结束不清理
- workspace.service.ts 元数据插入边界问题
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
*
|
||||
* 三级权限模型:Read / Write / External Action
|
||||
*
|
||||
* v0.3.0 增强:
|
||||
* - 实现 maxFrequency 频率限制(滑动窗口算法)
|
||||
* - 添加 checkFrequency 和 recordCall 方法
|
||||
* - 添加 cleanupFrequencyRecords 防止内存泄漏
|
||||
*
|
||||
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第十章
|
||||
*/
|
||||
|
||||
@@ -22,12 +27,14 @@ export interface PermissionPolicy {
|
||||
}
|
||||
|
||||
export const DEFAULT_POLICIES: PermissionPolicy[] = [
|
||||
{ toolName: 'read_file', requiredLevel: PermissionLevel.READ, deniedPatterns: [/\/etc\//, /\/proc\//, /C:\\Windows\\/i, /C:\\System32\\/i, /MEMORY\.md/i] },
|
||||
// v0.3.0 修复:deniedPatterns 使用 (?:\/|["'\s,}]|$) 匹配,
|
||||
// 覆盖 /etc/ 和 /etc(无尾斜杠,在 JSON 字符串中后跟引号的情况)
|
||||
{ toolName: 'read_file', requiredLevel: PermissionLevel.READ, deniedPatterns: [/\/etc(?:\/|["'\s,}]|$)/, /\/proc(?:\/|["'\s,}]|$)/, /C:\\Windows\\/i, /C:\\System32\\/i, /MEMORY\.md/i] },
|
||||
{ toolName: 'web_search', requiredLevel: PermissionLevel.READ, maxFrequency: 10 },
|
||||
{ toolName: 'list_directory', requiredLevel: PermissionLevel.READ },
|
||||
{ toolName: 'search_files', requiredLevel: PermissionLevel.READ },
|
||||
{ toolName: 'memory_search', requiredLevel: PermissionLevel.READ },
|
||||
{ toolName: 'write_file', requiredLevel: PermissionLevel.WRITE, deniedPatterns: [/\/etc\//, /\/proc\//, /\/System\//, /C:\\Windows\\/i, /C:\\System32\\/i, /MEMORY\.md/i], requireConfirmation: true, maxFrequency: 5 },
|
||||
{ toolName: 'write_file', requiredLevel: PermissionLevel.WRITE, deniedPatterns: [/\/etc(?:\/|["'\s,}]|$)/, /\/proc(?:\/|["'\s,}]|$)/, /\/System(?:\/|["'\s,}]|$)/, /C:\\Windows\\/i, /C:\\System32\\/i, /MEMORY\.md/i], requireConfirmation: true, maxFrequency: 5 },
|
||||
{ toolName: 'memory_store', requiredLevel: PermissionLevel.WRITE },
|
||||
{ toolName: 'run_command', requiredLevel: PermissionLevel.EXTERNAL_ACTION, deniedPatterns: [/MEMORY\.md/i], requireConfirmation: true, maxFrequency: 3 },
|
||||
{ toolName: 'web_fetch', requiredLevel: PermissionLevel.READ },
|
||||
@@ -39,12 +46,30 @@ export const DEFAULT_POLICIES: PermissionPolicy[] = [
|
||||
export class PolicyEngine {
|
||||
private policies: Map<string, PermissionPolicy> = new Map();
|
||||
|
||||
/** v0.3.0: 工具调用频率追踪 — 工具名 -> 调用时间戳列表 */
|
||||
private callFrequency: Map<string, number[]> = new Map();
|
||||
|
||||
/** v0.3.0: 频率限制的时间窗口(1分钟 = 60秒) */
|
||||
private readonly FREQ_WINDOW_MS = 60_000;
|
||||
|
||||
/**
|
||||
* v0.3.0 修复:customPolicies 与 DEFAULT_POLICIES 合并而非完全覆盖
|
||||
*
|
||||
* 合并策略:customPolicies 中的字段覆盖默认策略的同名字段,
|
||||
* 未指定的字段保留默认值(如 deniedPatterns 等安全配置不会被丢失)
|
||||
*/
|
||||
constructor(customPolicies: PermissionPolicy[] = []) {
|
||||
for (const policy of DEFAULT_POLICIES) {
|
||||
this.policies.set(policy.toolName, policy);
|
||||
this.policies.set(policy.toolName, { ...policy });
|
||||
}
|
||||
for (const policy of customPolicies) {
|
||||
this.policies.set(policy.toolName, policy);
|
||||
const existing = this.policies.get(policy.toolName);
|
||||
if (existing) {
|
||||
// v0.3.0 修复:合并而非替换,保留默认的安全配置(如 deniedPatterns)
|
||||
this.policies.set(policy.toolName, { ...existing, ...policy });
|
||||
} else {
|
||||
this.policies.set(policy.toolName, policy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +90,14 @@ export class PolicyEngine {
|
||||
};
|
||||
}
|
||||
|
||||
const argsStr = JSON.stringify(args);
|
||||
// v0.3.0 修复:使用 try-catch 防止循环引用导致 JSON.stringify 抛错
|
||||
let argsStr: string;
|
||||
try {
|
||||
argsStr = JSON.stringify(args);
|
||||
} catch {
|
||||
// 循环引用等异常情况,降级为 toString
|
||||
argsStr = String(args);
|
||||
}
|
||||
|
||||
// v0.2.0: allowedPatterns 白名单校验 — 若定义了白名单,参数必须匹配其中之一
|
||||
if (policy.allowedPatterns && policy.allowedPatterns.length > 0) {
|
||||
@@ -99,10 +131,79 @@ export class PolicyEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// v0.3.0: 频率限制检查
|
||||
if (policy.maxFrequency !== undefined) {
|
||||
const freqCheck = this.checkFrequency(toolName, policy.maxFrequency);
|
||||
if (!freqCheck.allowed) {
|
||||
return {
|
||||
authorized: false,
|
||||
reason: freqCheck.reason,
|
||||
level: policy.requiredLevel,
|
||||
requiresConfirmation: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
authorized: true,
|
||||
level: policy.requiredLevel,
|
||||
requiresConfirmation: policy.requireConfirmation ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.0: 频率限制检查(滑动窗口算法)
|
||||
*
|
||||
* 检查指定工具在时间窗口内的调用次数是否超过限制。
|
||||
* 注意:此方法仅检查,不记录调用。调用成功后需调用 recordCall()。
|
||||
*
|
||||
* v0.3.0 修复:
|
||||
* - 将 validCalls 写回 Map,避免 callFrequency 数组无限增长(内存泄漏)
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @param maxFreq 最大频率(每分钟)
|
||||
* @returns 检查结果
|
||||
*/
|
||||
checkFrequency(toolName: string, maxFreq?: number): { allowed: boolean; reason?: string } {
|
||||
const policy = this.policies.get(toolName);
|
||||
const limit = maxFreq ?? policy?.maxFrequency;
|
||||
if (limit === undefined) return { allowed: true };
|
||||
|
||||
const now = Date.now();
|
||||
const calls = this.callFrequency.get(toolName) ?? [];
|
||||
// 移除时间窗口外的调用记录
|
||||
const validCalls = calls.filter((t) => now - t < this.FREQ_WINDOW_MS);
|
||||
|
||||
// v0.3.0 修复:将清理后的 validCalls 写回 Map,避免数组无限增长
|
||||
if (validCalls.length !== calls.length) {
|
||||
this.callFrequency.set(toolName, validCalls);
|
||||
}
|
||||
|
||||
if (validCalls.length >= limit) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Rate limit exceeded for ${toolName}: max ${limit} calls per minute (current: ${validCalls.length})`,
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.0: 记录工具调用(工具成功执行后调用)
|
||||
*
|
||||
* v0.3.0 修复:同时清理过期记录,防止数组无限增长
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
*/
|
||||
recordCall(toolName: string): void {
|
||||
const now = Date.now();
|
||||
const calls = this.callFrequency.get(toolName) ?? [];
|
||||
// v0.3.0 修复:记录新调用时同时清理过期记录
|
||||
const validCalls = calls.filter((t) => now - t < this.FREQ_WINDOW_MS);
|
||||
validCalls.push(now);
|
||||
this.callFrequency.set(toolName, validCalls);
|
||||
}
|
||||
|
||||
// v0.3.0 修复: cleanupFrequencyRecords 已删除 — checkFrequency 和 recordCall 已做内联清理,
|
||||
// 该方法属于死代码,删除以减少维护负担
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user