feat: v0.7.0 四阶段全量迭代 — 修复面收口 · 安全纵深 · 架构还债 · 能力演进
P1 修复面收口: v0.6.3 截断自愈推全量(Anthropic/Ollama/非流式/引擎兜底); SSE 上游错误帧检测进重试通道; clearMessages 摘要游标根治; truncateResult 内联图片白名单统一; 前端四 bug(确认弹窗锁死/MemoryViewer/ Virtuoso Footer/abort 尾部过滤) + reasoning 缓冲跨迭代污染; 托盘通知过滤与新建会话死链接线 P2 安全纵深: MCP 审批闭环(ConfirmationHook×PolicyEngine 联动+重名拒注册); SSRF 收敛 ssrf-guard 共享模块 (web_fetch 双通道校验+重定向终态复检); Electron 加固(preload CJS 化→sandbox:true/CSP/权限白名单/will-navigate); run_command cmd.exe 白名单通道元字符守门; diff_viewer 10MB 预检; Anthropic thinking 预算下限; Agnes 思考显式关闭 P3 架构还债: OpenAICompatibleAdapter 中间基类收敛四家样板; 错误分类单轨化(删 mapError/getFetchSignal, 超时显式 ETIMEDOUT); PRAGMA user_version 迁移版本化; 死代码清理专项(cn.ts/SHORTCUTS/ContextMenu 分支/ getWindowState/modifiedArgs/sandbox 空壳); i18next 引入; a11y 第一轮; SearXNG 页批量草稿模型统一 P4 能力演进: Ollama pull 可取消/capabilities 探测/num_ctx 实测缓存; UpdateService feed 比对式自动更新 (app:updateCheck IPC + StatusBar 入口); MiMo providerOptions(web_search 服务端工具/strict JSON); web_fetch extract_mode=markdown(turndown); network.proxyUrl 全局代理(Chromium sessions+undici dispatcher) 测试: 264 → 507 用例(Electron ABI 全绿零跳过), 覆盖引擎压缩管线/重试竞速/MEMORY.md 闸门/file_editor 五操作/ filesystem 七工具实体夹具/git 真实仓库/SSE 错误帧/全线截断自愈/Provider 请求形态矩阵/SSRF 表测/钩子分级矩阵/ OutputValidator 全量/SLO 指标/MCP 安全纯函数/task_manager 链路/渲染层纯域/i18n 桥契约
This commit is contained in:
+77
-9
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import 'dotenv/config';
|
||||
import { app, shell, Menu, BrowserWindow, dialog } from 'electron';
|
||||
import { app, shell, Menu, BrowserWindow, dialog, session } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { electronApp, optimizer } from '@electron-toolkit/utils';
|
||||
@@ -90,6 +90,7 @@ import { PromptInjectionDefender } from './harness/security/prompt-injection-def
|
||||
import { OutputValidator } from './harness/verification/output-validator';
|
||||
import { TaskOrchestrator } from './harness/orchestration/orchestrator';
|
||||
import { HealthChecker, SLOMonitor } from './utils/slo';
|
||||
// v0.6.4 P4-5: session 级网络代理应用工具(default + agent-browser 分区)
|
||||
|
||||
// ===== 步骤 1: 初始化日志系统(SYS 层)=====
|
||||
log.transports.file.level = 'info';
|
||||
@@ -302,6 +303,16 @@ async function initialize(): Promise<void> {
|
||||
// fail-secure: 仅显式 false 才关闭 —— 配置值异常(空串/null/类型错误)时保持默认开启
|
||||
const promptInjectionEnabled =
|
||||
configService.get<boolean>('security.promptInjectionDefense') !== false;
|
||||
// v0.6.4 P4-5: 应用启动即按配置/环境变量设置 session 级代理(变更在 shared.ts 侧联动)
|
||||
void (async () => {
|
||||
try {
|
||||
const { applySessionProxy } = await import('./utils/network-proxy');
|
||||
await applySessionProxy(configService.get<string>('network.proxyUrl') ?? null);
|
||||
} catch (err) {
|
||||
log.warn('[Network] Initial proxy application failed:', err);
|
||||
}
|
||||
})();
|
||||
|
||||
const auditEnabled = configService.get<boolean>('logging.auditEnabled') !== false;
|
||||
const traceEnabled = configService.get<boolean>('logging.traceEnabled') !== false;
|
||||
// SessionRecorder 录制总开关(false 时不写 TRACE JSONL 文件)
|
||||
@@ -311,13 +322,15 @@ async function initialize(): Promise<void> {
|
||||
const policyEngine = new PolicyEngine();
|
||||
const sandboxManager = new SandboxManager({
|
||||
allowedPaths: [workspaceInfo.path],
|
||||
networkPolicy: 'allowlist',
|
||||
});
|
||||
const promptDefender = new PromptInjectionDefender();
|
||||
const outputValidator = new OutputValidator();
|
||||
|
||||
// v0.2.0: ConfirmationHook(提前创建,mainWindow 创建后再注入)
|
||||
// v0.6.4 P2-1: 注入 PolicyEngine —— 消费策略级 requireConfirmation(mcp_* 通配
|
||||
// 策略),修复"外部 MCP 工具全部免确认执行"的跨层防线不一致
|
||||
const confirmationHook = new ConfirmationHook(null, configService);
|
||||
confirmationHook.setPolicyEngine(policyEngine);
|
||||
|
||||
// ===== 步骤 6: 注册内置工具 =====
|
||||
const toolRegistry = new ToolRegistry();
|
||||
@@ -703,13 +716,22 @@ async function initialize(): Promise<void> {
|
||||
);
|
||||
|
||||
// ===== Agent 完成时发送系统通知 =====
|
||||
agentEngineManager.on('complete', (data: { sessionId: string; durationMs: number }) => {
|
||||
trayManager?.sendNotification(
|
||||
'MetonaAI — 任务完成',
|
||||
`Agent 已完成任务 (${(data.durationMs / 1000).toFixed(1)}s)`,
|
||||
() => windowManager?.focusWindow(),
|
||||
);
|
||||
});
|
||||
// v0.6.4 修复(B-6): 仅在任务真正完成时弹出系统通知 —— 引擎对 user_interrupt /
|
||||
// timeout / dead_loop / error 等也走统一 finishRun 发出 complete 事件,
|
||||
// 原实现让"用户主动停止"也会收到「Agent 已完成任务」的误导性通知。
|
||||
agentEngineManager.on(
|
||||
'complete',
|
||||
(data: { sessionId: string; durationMs: number; terminationReason?: string }) => {
|
||||
if (data.terminationReason !== undefined && data.terminationReason !== 'completed') {
|
||||
return;
|
||||
}
|
||||
trayManager?.sendNotification(
|
||||
'MetonaAI — 任务完成',
|
||||
`Agent 已完成任务 (${(data.durationMs / 1000).toFixed(1)}s)`,
|
||||
() => windowManager?.focusWindow(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// ===== P1-12: SLO 健康监控接入(原为死代码,现真实运行) =====
|
||||
const healthChecker = new HealthChecker(
|
||||
@@ -885,6 +907,52 @@ app
|
||||
app.exit(1);
|
||||
});
|
||||
|
||||
// ===== v0.6.4 安全加固:进程级权限防线(P2-3)=====
|
||||
// 此前主窗口无 CSP、无 permission handler —— notifications/geo/media/clipboard
|
||||
// 等请求全部走 Chromium 默认放行,且渲染层一旦被注入可静默触达敏感能力。
|
||||
// 两条防线均为 deny-by-default 白名单制,任何一条失败都不放大攻击面。
|
||||
{
|
||||
// 防线一:权限请求白名单(deny-by-default)
|
||||
const ALLOWED_PERMISSIONS = new Set<string>(['clipboard-sanitized-write', 'fullscreen']);
|
||||
session.defaultSession.setPermissionRequestHandler((_wc, permission, callback) => {
|
||||
callback(ALLOWED_PERMISSIONS.has(permission));
|
||||
});
|
||||
session.defaultSession.setPermissionCheckHandler((_wc, permission) =>
|
||||
ALLOWED_PERMISSIONS.has(permission),
|
||||
);
|
||||
|
||||
// 防线二:生产环境 CSP 注入(仅 mainFrame,不触碰 dev server 的 HMR)。
|
||||
// MUI/emotion 需要 style-src 'unsafe-inline'(运行时注入 <style> 标签与 style 属性);
|
||||
// 附件预览/截图使用 data:/blob: 图片;渲染进程本身不直接外联(所有 fetch 在主进程)。
|
||||
if (!process.env['ELECTRON_RENDERER_URL']) {
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
if (details.resourceType === 'mainFrame') {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [
|
||||
[
|
||||
"default-src 'self'",
|
||||
"script-src 'self'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: blob:",
|
||||
"font-src 'self' data:",
|
||||
"connect-src 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'none'",
|
||||
].join('; '),
|
||||
],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
callback({ responseHeaders: details.responseHeaders });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.on('web-contents-created', (_, contents) => {
|
||||
// C-8 修复: 全局 web-contents 监听器也校验 URL 协议
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
|
||||
Reference in New Issue
Block a user