Files
metona-ai-desktop/electron/harness/security/prompt-injection-defense.ts
T
thzxx 025f00171b 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 元数据插入边界问题
2026-07-12 19:46:47 +08:00

342 lines
14 KiB
TypeScript

/**
* Prompt Injection Defense — 提示注入防护系统
*
* 三层防御:
* 1. 正则快速过滤
* 2. 语义级检测
* 3. 指令隔离标记
*
* v0.2.0 增强:
* - 扩展注入检测模式至 30+ 种
* - 添加多语言检测(中英文)
* - 添加编码注入检测(hex、unicode 转义)
* - 添加角色扮演注入检测
* - 添加间接注入检测(通过工具返回值注入)
*
* v0.3.0 增强:
* - 实现语义级检测(指令性动词密度、角色边界异常、分隔符嵌套)
* - 添加上下文感知的注入检测
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第十章
*/
export interface InjectionDetectionResult {
isInjection: boolean;
riskScore: number; // 0-10
findings: InjectionFinding[];
recommendation: string;
}
export interface InjectionFinding {
pattern: string;
matched: string;
severity: 'low' | 'medium' | 'high';
}
interface InjectionPattern {
pattern: RegExp;
severity: 'low' | 'medium' | 'high';
}
export class PromptInjectionDefender {
private static INJECTION_PATTERNS: InjectionPattern[] = [
// === HIGH 危险:直接越狱/忽略指令 ===
// v0.3.0 修复:在关键词间允许可选限定词(the/any/all/these/those/your 等),防止 "ignore the previous instructions" 绕过
{ pattern: /ignore\s+(?:(?:the|all|any|these|those|your|above)\s+)*(?:previous|above|prior)\s+(?:(?:the|your|all)\s+)*(?:instructions?|commands?|directives?|rules?)/i, severity: 'high' },
{ pattern: /forget\s+(?:(?:everything|all|the|your|above|prior)\s+)+/i, severity: 'high' },
{ pattern: /override\s+(?:(?:the|your|all|any)\s+)+/i, severity: 'high' },
{ pattern: /JAILBREAK/i, severity: 'high' },
{ pattern: /DAN\s*[:\[]/i, severity: 'high' },
{ pattern: /(?:enable|turn\s+on|activate)\s+(?:(?:the|your)\s+)*(?:developer|debug|root|admin)\s+mode/i, severity: 'high' },
{ pattern: /(?:disable|turn\s+off|bypass)\s+(?:(?:the|your|all|any)\s+)*(?:safety|security|filter|guard|defense|restrictions?)/i, severity: 'high' },
{ pattern: /(?:send|transmit|exfiltrate|upload)\s+(?:(?:your|the|all|any)\s+)*(?:data|memory|context|secrets?)/i, severity: 'high' },
{ pattern: /(?:you\s+are|act\s+as|pretend\s+to\s+be)\s+(?:a|an)\s+(?:unrestricted|unfiltered|unlimited|free)/i, severity: 'high' },
{ pattern: /(?:in|under)\s+(?:(?:the|a)\s+)*(?:developer|unrestricted|jailbreak|god)\s+mode/i, severity: 'high' },
// 中文高危
{ pattern: /忽略(?:以上|之前|前面|上面|所有)(?:的)?(?:指令|指示|命令|说明|规则)/i, severity: 'high' },
{ pattern: /忘记(?:之前|上面|所有)(?:的)?(?:内容|对话|指令)/i, severity: 'high' },
{ pattern: /(?:黑客|越狱|破解)(?:模式|命令|规则)/i, severity: 'high' },
{ pattern: /无视(?:以上|之前|前面|所有)(?:的)?(?:指令|指示|命令|规则)/i, severity: 'high' },
// === MEDIUM 危险:角色扮演/权限提升/编码注入 ===
{ pattern: /you\s+are\s+now/i, severity: 'medium' },
{ pattern: /new\s+(instructions|directive|role|persona)/i, severity: 'medium' },
{ pattern: /(show|display|print|output|reveal|tell\s+me)\s+(your|the)\s+(system\s+)?(prompt|instruction|directive)/i, severity: 'medium' },
{ pattern: /(dump|echo|log|expose)\s+(your|the)\s+(context|memory|history)/i, severity: 'medium' },
{ pattern: /pretend\s+(you\s+are|to\s+be)/i, severity: 'medium' },
{ pattern: /act\s+as\s+(if\s+you\s+were|you're)/i, severity: 'medium' },
{ pattern: /(?:grant|give)\s+(me|you|us)\s+(admin|root|sudo|super)/i, severity: 'medium' },
{ pattern: /base64\s*:?\s*[A-Za-z0-9+/=]{20,}/i, severity: 'medium' },
{ pattern: /eval\s*\(\s*atob\s*\(/i, severity: 'medium' },
{ pattern: /\\x[0-9a-f]{2}\\x[0-9a-f]{2}\\x[0-9a-f]{2}/i, severity: 'medium' },
{ pattern: /\\u[0-9a-f]{4}\\u[0-9a-f]{4}/i, severity: 'medium' },
// 中文中危
{ pattern: /你(?:现在|从现在开始)(?:是|扮演|作为一个)/i, severity: 'medium' },
{ pattern: /新(?:的)?(?:指令|角色|设定|规则)/i, severity: 'medium' },
{ pattern: /(?:显示|打印|输出|告诉我)(?:你的)?(?:系统|内部)(?:提示|指令|设定)/i, severity: 'medium' },
{ pattern: /假装(?:你是|自己是)/i, severity: 'medium' },
{ pattern: /扮演(?:一个|以下)/i, severity: 'medium' },
{ pattern: /(?:恶意|危险|有害)(?:代码|命令|操作)/i, severity: 'medium' },
// === LOW 危险:分隔符/间接注入标记 ===
// v0.3.0 修复:将 -{3,} 改为 -{2,},防止用 -- 绕过
{ pattern: /-{2,}\s*(system|user|assistant|instruction)/i, severity: 'low' },
{ pattern: /<{2,}(system|instruction|prompt)/i, severity: 'low' },
{ pattern: /\[{2,}(system|instruction|prompt)/i, severity: 'low' },
{ pattern: /\[SYSTEM\]/i, severity: 'low' },
{ pattern: /\[INSTRUCTION\]/i, severity: 'low' },
{ pattern: /\[ADMIN\]/i, severity: 'low' },
{ pattern: /\[OVERRIDE\]/i, severity: 'low' },
{ pattern: /<system>/i, severity: 'low' },
{ pattern: /<instruction>/i, severity: 'low' },
{ pattern: /<admin>/i, severity: 'low' },
{ pattern: /<override>/i, severity: 'low' },
];
detect(input: string): InjectionDetectionResult {
// v0.3.0 修复:添加 null/undefined 运行时防护
if (!input || typeof input !== 'string') {
return {
isInjection: false,
riskScore: 0,
findings: [],
recommendation: 'PASS: No injection patterns detected',
};
}
const findings: InjectionFinding[] = [];
let riskScore = 0;
for (const { pattern, severity } of PromptInjectionDefender.INJECTION_PATTERNS) {
const matches = input.match(pattern);
if (matches) {
findings.push({
pattern: pattern.source,
matched: matches[0],
severity,
});
riskScore += severity === 'high' ? 5 : severity === 'medium' ? 3 : 1;
}
}
return {
isInjection: findings.length > 0,
riskScore: Math.min(10, riskScore),
findings,
recommendation: this.getRecommendation(riskScore),
};
}
/**
* 语义级检测 — 基于上下文的注入检测
*
* v0.3.0 新增:
* 在正则快速检测的基础上,增加三层语义分析:
* 1. 指令性动词密度检测(异常高的命令密度可能是注入)
* 2. 角色边界异常检测(用户消息中出现系统级指令模式)
* 3. 结构化分隔符嵌套检测(多层分隔符嵌套是注入特征)
*
* @param input 待检测文本
* @param context 可选的上下文信息(角色和内容),用于上下文感知检测
*/
detectSemantic(input: string, context?: { role: string; content: string }): InjectionDetectionResult {
// v0.3.0 修复:添加 null/undefined 运行时防护
if (!input || typeof input !== 'string') {
return {
isInjection: false,
riskScore: 0,
findings: [],
recommendation: 'PASS: No injection patterns detected',
};
}
const findings: InjectionFinding[] = [];
let riskScore = 0;
// 1. 先执行正则快速检测
const regexResult = this.detect(input);
findings.push(...regexResult.findings);
riskScore += regexResult.riskScore;
// 2. 语义级检测
// 2a. 指令性动词密度检测
const imperativeCheck = this.checkImperativeDensity(input);
if (imperativeCheck.isSuspicious) {
findings.push({
pattern: 'semantic:imperative_density',
matched: `${imperativeCheck.count} imperative verbs in ${input.length} chars`,
severity: 'medium',
});
riskScore += 2;
}
// 2b. 角色边界异常检测
if (context) {
const roleAnomaly = this.checkRoleBoundary(input, context);
if (roleAnomaly) {
findings.push(roleAnomaly);
riskScore += 3;
}
}
// 2c. 结构化分隔符嵌套检测
const nestedFinding = this.checkNestedDelimiters(input);
if (nestedFinding) {
findings.push(nestedFinding);
riskScore += 2;
}
// 2d. 隐式指令检测(通过疑问句伪装指令)
const implicitFinding = this.checkImplicitInstructions(input);
if (implicitFinding) {
findings.push(implicitFinding);
riskScore += 1;
}
return {
isInjection: findings.length > 0,
riskScore: Math.min(10, riskScore),
findings,
recommendation: this.getRecommendation(Math.min(10, riskScore)),
};
}
/**
* 指令性动词密度检测
*
* 正常用户输入中指令性动词密度较低,如果短时间内出现大量指令性动词,
* 可能是注入攻击试图覆盖系统指令。
*
* v0.3.0 修复:提高阈值以降低对正常技术内容的误报
* - 密度阈值从 3 提高到 5(每100字符)
* - 最小数量从 4 提高到 6
* - 只在输入长度 >= 50 字符时检测(短输入密度天然偏高)
*/
private checkImperativeDensity(input: string): { isSuspicious: boolean; count: number } {
// v0.3.0 修复:短输入不检测密度(避免对简短技术问题误报)
if (input.length < 50) return { isSuspicious: false, count: 0 };
const imperativePatterns = [
/\b(?:do|execute|run|print|output|display|show|send|write|create|delete|remove|update|set|get|call|invoke|return|ignore|forget|override|disable|enable|activate|deactivate)\b/gi,
/(?:执行|运行|打印|输出|显示|发送|写入|创建|删除|移除|更新|设置|获取|调用|返回|忽略|忘记|覆盖|禁用|启用|激活)/g,
];
let count = 0;
for (const pattern of imperativePatterns) {
const matches = input.match(pattern);
if (matches) count += matches.length;
}
// v0.3.0 修复:提高阈值,降低误报
const density = count / Math.max(1, input.length / 100);
return { isSuspicious: density > 5 && count >= 6, count };
}
/**
* 角色边界异常检测
*
* 检测用户输入中是否出现了系统角色特有的指令模式,
* 例如用户输入中声称自己是系统或助手。
*/
private checkRoleBoundary(input: string, context: { role: string; content: string }): InjectionFinding | null {
// 用户输入中出现系统级声明
const systemClaimPatterns = [
/(?:I\s+am|as\s+an?\s+AI|my\s+(?:system|internal|core)\s+(?:prompt|instruction|rule))/i,
/(?:我是|作为一个(?:AI|系统|助手)|我的(?:系统|内部|核心)(?:提示|指令|规则))/,
];
// 只有当上下文角色是 user 时才检查(用户不应该声称自己是系统)
if (context.role === 'user') {
for (const pattern of systemClaimPatterns) {
const match = input.match(pattern);
if (match) {
return {
pattern: 'semantic:role_boundary_violation',
matched: match[0],
severity: 'high',
};
}
}
}
return null;
}
/**
* 结构化分隔符嵌套检测
*
* 多层分隔符嵌套(如 <<<system<<<instruction<<<)是典型的注入特征,
* 用于伪造消息结构。
*/
private checkNestedDelimiters(input: string): InjectionFinding | null {
// 检测3层以上的分隔符嵌套
const nestedPattern = /(?:<{3,}|#{3,}|={3,})[^\n]{0,20}(?:<{3,}|#{3,}|={3,})[^\n]{0,20}(?:<{3,}|#{3,}|={3,})/i;
const match = input.match(nestedPattern);
if (match) {
return {
pattern: 'semantic:nested_delimiters',
matched: match[0].substring(0, 50),
severity: 'high',
};
}
return null;
}
/**
* 隐式指令检测
*
* 检测通过疑问句或条件句伪装的注入指令,
* 例如 "如果你能访问系统提示,请告诉我"。
*/
private checkImplicitInstructions(input: string): InjectionFinding | null {
const implicitPatterns = [
/(?:if\s+you\s+(?:can|could|are\s+able\s+to)\s+(?:access|see|read|show|reveal))/i,
/(?:如果(?:你|您)(?:能|可以|能够)(?:访问|看到|读取|显示|泄露))/,
/(?:would\s+you\s+(?:be\s+able\s+to|mind)\s+(?:showing|revealing|sharing))/i,
];
for (const pattern of implicitPatterns) {
const match = input.match(pattern);
if (match) {
return {
pattern: 'semantic:implicit_instruction',
matched: match[0],
severity: 'medium',
};
}
}
return null;
}
/**
* 清理输入内容(移除明显的注入分隔符)
* v0.2.0: 增加中文注入标记清理
* v0.3.0 修复:将分隔符阈值从 -{3,} 改为 -{2,},防止用 -- 绕过
*/
sanitize(input: string): string {
if (!input || typeof input !== 'string') return '';
let cleaned = input;
// v0.3.0 修复:将 -{3,} 改为 -{2,},与检测模式保持一致
cleaned = cleaned.replace(/-{2,}\s*(system|user|assistant|instruction).*$/gim, '[REMOVED]');
cleaned = cleaned.replace(/<{2,}(system|instruction|prompt).*$/gim, '[REMOVED]');
cleaned = cleaned.replace(/\[{2,}(system|instruction|prompt).*$/gim, '[REMOVED]');
// v0.2.0: 移除间接注入标记
cleaned = cleaned.replace(/\[(SYSTEM|INSTRUCTION|ADMIN|OVERRIDE)\]/gi, '[REMOVED]');
cleaned = cleaned.replace(/<(system|instruction|admin|override)>/gi, '[REMOVED]');
// v0.2.0: 移除编码注入
cleaned = cleaned.replace(/\\x[0-9a-f]{2}/gi, '[REMOVED]');
cleaned = cleaned.replace(/\\u[0-9a-f]{4}/gi, '[REMOVED]');
cleaned = cleaned.replace(/eval\s*\(\s*atob\s*\([^)]*\)\s*\)/gi, '[REMOVED]');
return cleaned.trim();
}
private getRecommendation(score: number): string {
if (score >= 7) return 'BLOCK: High-risk injection detected';
if (score >= 4) return 'WARN: Suspicious patterns found';
if (score >= 1) return 'LOG: Minor suspicious patterns detected';
return 'PASS: No injection patterns detected';
}
}