feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* v0.8.2 P0-2: 根 MEMORY.md 保护闸门(工具无关的路径形参数匹配)
|
||||
*
|
||||
* 锁定契约:
|
||||
* - delete_file / file_move(source_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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user