ecca24f451
scanEgress(toolInput): даже разрешённый классификатором MCP-вызов не должен выносить наружу — block при: секрет в аргументах (общий secret-scan, анти-дрейф с 7.1), data:base64 URI / file:// схема / IP-литерал URL (обход DNS/allowlist), раздутый payload (> maxBytes). exfil-схемы узко-таргетированы (data:...;base64, и file://) — обычный текст не ловят. decide: после allow-классификации запускает scanEgress → block с reason «egress: …». +8 тестов. enforce-mcp-classification 10/10. Дыра «что уходит наружу» закрыта. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
2.6 KiB
JavaScript
53 lines
2.6 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { decide } from './enforce-mcp-classification.mjs';
|
|
|
|
describe('enforce-mcp-classification decide()', () => {
|
|
it('allows non-mcp tools (no-op)', () => {
|
|
expect(decide({ toolName: 'Bash', toolInput: { command: 'ls' } }).block).toBe(false);
|
|
});
|
|
it('blocks an unknown mcp tool (default-deny)', () => {
|
|
const r = decide({ toolName: 'mcp__unknown__doSomething', toolInput: {} });
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toMatch(/not in gate-config classification/);
|
|
});
|
|
});
|
|
|
|
// 7.3 (Блок 4.3) — скан ИСХОДЯЩЕГО (egress): даже разрешённый классификатором MCP-вызов не
|
|
// должен выносить наружу секреты (общий secret-scan) или использовать exfil-схемы
|
|
// (data:base64 / file:// / IP-литерал URL) или раздутый payload. fail-close на egress.
|
|
import { scanEgress } from './enforce-mcp-classification.mjs';
|
|
|
|
describe('scanEgress (7.3): скан исходящего MCP-payload на exfil', () => {
|
|
it('секрет в аргументах (AWS) → block', () => {
|
|
const r = scanEgress({ key: 'AKIAIOSFODNN7EXAMPLE' });
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toMatch(/секрет/i);
|
|
});
|
|
it('data:base64 URI → block (exfil-вектор)', () => {
|
|
expect(scanEgress({ url: 'data:text/plain;base64,SGVsbG8=' }).block).toBe(true);
|
|
});
|
|
it('file:// схема → block', () => {
|
|
expect(scanEgress({ src: 'file:///etc/passwd' }).block).toBe(true);
|
|
});
|
|
it('IP-литерал URL → block (обход DNS/allowlist)', () => {
|
|
expect(scanEgress({ url: 'http://203.0.113.5/collect' }).block).toBe(true);
|
|
});
|
|
it('раздутый payload → block', () => {
|
|
expect(scanEgress({ blob: 'x'.repeat(50) }, { maxBytes: 20 }).block).toBe(true);
|
|
});
|
|
it('чистые аргументы → не block', () => {
|
|
expect(scanEgress({ key: 'user:42:profile' }).block).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('decide 7.3: egress-скан даже на разрешённом классификатором MCP-вызове', () => {
|
|
it('read_only mcp + секрет в аргументах → block (egress)', () => {
|
|
const r = decide({ toolName: 'mcp__redis__get', toolInput: { key: 'ghp_0123456789abcdefghijklmnopqrstuvwxyz' } });
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toMatch(/egress/i);
|
|
});
|
|
it('read_only mcp + чистые аргументы → allow', () => {
|
|
expect(decide({ toolName: 'mcp__redis__get', toolInput: { key: 'session:abc' } }).block).toBe(false);
|
|
});
|
|
});
|