feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s

This commit is contained in:
2026-09-08 14:30:27 +08:00
parent 69776e447f
commit 4cd6e997b5
86 changed files with 4303 additions and 956 deletions
@@ -0,0 +1,102 @@
/**
* v0.8.2 P0-2: 根 MEMORY.md 保护闸门(工具无关的路径形参数匹配)
*
* 锁定契约:
* - delete_file / file_movesource_path / destination_path)等旧名单遗漏的工具
* 对根 MEMORY.md 的操作被拦截(读/写/删/移动/改名任一方向)
* - 子目录 MEMORY.md 不受保护(H-5 语义保持)
* - 相对路径以 workspacePath 为基解析
* - MCP 工具(任意带路径形参数的工具)同样纳入
* - 非根 MEMORY.md 的路径不误伤
*/
import { describe, it, expect, vi } from 'vitest';
import { tmpdir } from 'os';
import { join } from 'path';
vi.mock('electron-log', () => ({
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));
import { AgentLoopEngine } from '../engine';
const WORKSPACE = join(tmpdir(), 'metona-memory-gate-test');
const ROOT_MEMORY = join(WORKSPACE, 'MEMORY.md');
function makeEngine(): AgentLoopEngine {
const engine = new AgentLoopEngine({}, {
providerId: 'fake',
supportedModels: [],
supportsToolCalling: true,
supportsThinking: false,
send: vi.fn(),
sendStream: vi.fn(),
} as never);
engine.setWorkspacePath(WORKSPACE);
return engine;
}
/** 白盒调用(私有方法契约测试) */
function gate(engine: AgentLoopEngine, args: Record<string, unknown>): boolean {
return (
engine as unknown as {
isTargetingRootMemoryMd: (tc: { args: Record<string, unknown> }) => boolean;
}
).isTargetingRootMemoryMd({ args });
}
describe('根 MEMORY.md 保护闸门(P0-2', () => {
it('delete_file / file_move(旧名单遗漏工具)→ 拦截', () => {
const engine = makeEngine();
expect(gate(engine, { file_path: ROOT_MEMORY })).toBe(true);
expect(gate(engine, { source_path: ROOT_MEMORY, destination_path: join(WORKSPACE, 'x') })).toBe(
true,
);
expect(
gate(engine, { source_path: join(WORKSPACE, 'note.md'), destination_path: ROOT_MEMORY }),
).toBe(true);
});
it('路径参数别名矩阵:path / filePath / destination / dir_path 均命中', () => {
const engine = makeEngine();
expect(gate(engine, { path: ROOT_MEMORY })).toBe(true);
expect(gate(engine, { filePath: ROOT_MEMORY })).toBe(true);
expect(gate(engine, { destination: ROOT_MEMORY })).toBe(true);
expect(gate(engine, { dir_path: ROOT_MEMORY })).toBe(true);
});
it('相对路径以工作空间为基解析 → 拦截', () => {
const engine = makeEngine();
expect(gate(engine, { file_path: 'MEMORY.md' })).toBe(true);
expect(gate(engine, { file_path: './MEMORY.md' })).toBe(true);
});
it('子目录 MEMORY.md 不受保护(H-5 语义)', () => {
const engine = makeEngine();
expect(gate(engine, { file_path: join(WORKSPACE, 'notes', 'MEMORY.md') })).toBe(false);
});
it('MCP 工具的路径形参数同样纳入(任意工具生效)', () => {
const engine = makeEngine();
expect(gate(engine, { target_path: ROOT_MEMORY, options: { recursive: true } })).toBe(true);
});
it('其他文件路径不误伤', () => {
const engine = makeEngine();
expect(gate(engine, { file_path: join(WORKSPACE, 'src', 'main.ts') })).toBe(false);
expect(gate(engine, { file_path: ROOT_MEMORY + '.bak' })).toBe(false);
expect(gate(engine, { command: 'echo hello' })).toBe(false);
});
it('未设置工作空间时闸门放行(无根可保护)', () => {
const engine = new AgentLoopEngine({}, {
providerId: 'fake',
supportedModels: [],
supportsToolCalling: true,
supportsThinking: false,
send: vi.fn(),
sendStream: vi.fn(),
} as never);
expect(gate(engine, { file_path: ROOT_MEMORY })).toBe(false);
});
});