mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* feat(spring): model AOP advice and proxy behavior * fix(spring): address AOP review findings --------- Co-authored-by: Shining <xuenning@qiyi.com>
178 lines
6.8 KiB
TypeScript
178 lines
6.8 KiB
TypeScript
/**
|
|
* P0 Unit Tests: Security Hardening
|
|
*
|
|
* Tests security-related utility helpers in isolation:
|
|
* - Relation type allowlist
|
|
* - Path traversal detection
|
|
* - isTestFilePath patterns
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
VALID_RELATION_TYPES,
|
|
VALID_NODE_LABELS,
|
|
isTestFilePath,
|
|
} from '../../src/mcp/local/local-backend.js';
|
|
|
|
// ─── Relation type allowlist ──────────────────────────────────────────
|
|
|
|
describe('VALID_RELATION_TYPES', () => {
|
|
// The expected types are declared once here; the size assertion derives from
|
|
// the array length so adding a new type only requires appending to this list.
|
|
const EXPECTED_RELATION_TYPES = [
|
|
'CALLS',
|
|
'IMPORTS',
|
|
'EXTENDS',
|
|
'IMPLEMENTS',
|
|
'HAS_METHOD',
|
|
'HAS_PROPERTY',
|
|
'METHOD_OVERRIDES',
|
|
'OVERRIDES',
|
|
'METHOD_IMPLEMENTS',
|
|
'ACCESSES',
|
|
// USES is an emitted edge type (emit-references.ts) used in the default
|
|
// impact relTypes + context queries; added to the allowlist in F5.
|
|
'USES',
|
|
'HANDLES_ROUTE',
|
|
'FETCHES',
|
|
'HANDLES_TOOL',
|
|
'ENTRY_POINT_OF',
|
|
'WRAPS',
|
|
// Spring DI @Autowired collection injection (#2200)
|
|
'INJECTS',
|
|
// Conditional activation and metadata declaration/discovery (#2415)
|
|
'CONDITIONAL_ON',
|
|
'DECLARES',
|
|
// Spring proxy/advice evidence (#2416)
|
|
'ADVISED_BY',
|
|
] as const;
|
|
|
|
it('contains all expected relation types', () => {
|
|
expect(VALID_RELATION_TYPES.size).toBe(EXPECTED_RELATION_TYPES.length);
|
|
for (const t of EXPECTED_RELATION_TYPES) {
|
|
expect(VALID_RELATION_TYPES.has(t)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('rejects invalid relation types', () => {
|
|
expect(VALID_RELATION_TYPES.has('CONTAINS')).toBe(false);
|
|
expect(VALID_RELATION_TYPES.has('calls')).toBe(false); // case-sensitive
|
|
expect(VALID_RELATION_TYPES.has('DROP_TABLE')).toBe(false);
|
|
});
|
|
|
|
it('taint edge types stay OUT of the impact allow-list (#2083 M3 KTD9a)', () => {
|
|
// impact's BFS traverses symbol space; TAINTED/SANITIZES live in
|
|
// block-space (BasicBlock→BasicBlock) and would be unreachable noise
|
|
// there. The `explain` tool is the dedicated taint consumer. Pinned
|
|
// explicitly so a future "add all emitted types" sweep can't drag them in.
|
|
expect(VALID_RELATION_TYPES.has('TAINTED')).toBe(false);
|
|
expect(VALID_RELATION_TYPES.has('SANITIZES')).toBe(false);
|
|
});
|
|
|
|
it('TAINT_PATH stays OUT of the impact allow-list (#2084 M4 KTD9a)', () => {
|
|
// Cross-function TAINT_PATH (Function→Function) is the interprocedural
|
|
// analogue of TAINTED — surfaced ONLY via `explain` (its interprocedural
|
|
// findings), never impact()'s BFS. Pinned so a future allow-all sweep
|
|
// can't drag it in — the size assertion tracks EXPECTED_RELATION_TYPES.
|
|
expect(VALID_RELATION_TYPES.has('TAINT_PATH')).toBe(false);
|
|
// Size should match the expected types list — not a hardcoded number.
|
|
expect(VALID_RELATION_TYPES.size).toBe(EXPECTED_RELATION_TYPES.length);
|
|
});
|
|
|
|
it('CDG control-dependence edge types stay OUT of the impact allow-list (#2085 M5)', () => {
|
|
// CDG and POST_DOMINATE are BasicBlock→BasicBlock (block space), like the
|
|
// taint substrate — they must not enter impact()'s symbol-space BFS. Pinned
|
|
// explicitly (not just via the EXPECTED_RELATION_TYPES-derived size guard)
|
|
// so a future "add all emitted types" sweep can't drag them in, mirroring
|
|
// the TAINTED/TAINT_PATH pins.
|
|
expect(VALID_RELATION_TYPES.has('CDG')).toBe(false);
|
|
expect(VALID_RELATION_TYPES.has('POST_DOMINATE')).toBe(false);
|
|
// REACHING_DEF is the other BasicBlock→BasicBlock PDG edge (#2086 impact
|
|
// PDG mode traverses it directly, never through impact's symbol-space BFS).
|
|
// Pinned alongside CDG/POST_DOMINATE so an allow-all sweep can't drag it in.
|
|
expect(VALID_RELATION_TYPES.has('REACHING_DEF')).toBe(false);
|
|
});
|
|
});
|
|
|
|
// ─── Valid node labels ───────────────────────────────────────────────
|
|
|
|
describe('VALID_NODE_LABELS', () => {
|
|
it('contains core node types', () => {
|
|
for (const label of [
|
|
'File',
|
|
'Folder',
|
|
'Function',
|
|
'Class',
|
|
'Interface',
|
|
'Method',
|
|
'CodeElement',
|
|
]) {
|
|
expect(VALID_NODE_LABELS.has(label)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('contains meta node types', () => {
|
|
for (const label of ['Community', 'Process']) {
|
|
expect(VALID_NODE_LABELS.has(label)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('contains multi-language node types', () => {
|
|
for (const label of ['Struct', 'Enum', 'Macro', 'Trait', 'Impl', 'Namespace']) {
|
|
expect(VALID_NODE_LABELS.has(label)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('rejects invalid labels', () => {
|
|
expect(VALID_NODE_LABELS.has('InvalidType')).toBe(false);
|
|
expect(VALID_NODE_LABELS.has('function')).toBe(false); // case-sensitive
|
|
});
|
|
});
|
|
|
|
// ─── Path traversal detection ────────────────────────────────────────
|
|
|
|
describe('path traversal (isTestFilePath as proxy for path handling)', () => {
|
|
it('isTestFilePath matches .test. files', () => {
|
|
expect(isTestFilePath('src/foo.test.ts')).toBe(true);
|
|
expect(isTestFilePath('src/foo.spec.ts')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath matches __tests__ directory', () => {
|
|
expect(isTestFilePath('src/__tests__/foo.ts')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath matches /test/ directory', () => {
|
|
expect(isTestFilePath('src/test/foo.ts')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath handles Windows backslash paths', () => {
|
|
expect(isTestFilePath('src\\test\\foo.ts')).toBe(true);
|
|
expect(isTestFilePath('src\\__tests__\\bar.ts')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath is case-insensitive', () => {
|
|
expect(isTestFilePath('SRC/TEST/Foo.ts')).toBe(true);
|
|
expect(isTestFilePath('SRC/Foo.Test.ts')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath matches Go test files', () => {
|
|
expect(isTestFilePath('pkg/handler_test.go')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath matches Python test files', () => {
|
|
expect(isTestFilePath('tests/test_handler.py')).toBe(true);
|
|
expect(isTestFilePath('pkg/handler_test.py')).toBe(true);
|
|
});
|
|
|
|
it('isTestFilePath returns false for non-test files', () => {
|
|
expect(isTestFilePath('src/main.ts')).toBe(false);
|
|
expect(isTestFilePath('src/utils/helper.ts')).toBe(false);
|
|
});
|
|
|
|
it('isTestFilePath returns false for nodes without a filePath', () => {
|
|
// trace BFS visits Community/Process nodes whose rows carry no filePath;
|
|
// the guard must return false instead of throwing on undefined/null.
|
|
expect(isTestFilePath(undefined)).toBe(false);
|
|
expect(isTestFilePath(null)).toBe(false);
|
|
expect(isTestFilePath('')).toBe(false);
|
|
});
|
|
});
|