/**
* Unit Tests for Security Scanner
*
* Tests for secret detection and vulnerability scanning.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { SecurityScanner } from '../../src/mcp/validation/security-scanner.js';
import { PolicyEnforcer } from '../../src/mcp/validation/policy-enforcer.js';
import { DependencyChecker } from '../../src/mcp/validation/dependency-checker.js';
describe('Security Scanner', () => {
let scanner;
beforeEach(() => {
scanner = new SecurityScanner();
});
describe('scan', () => {
it('should return safe for clean content', async () => {
const result = await scanner.scan({
content: 'console.log("Hello World");'
});
expect(result.safe).toBe(true);
expect(result.issues).toHaveLength(0);
});
it('should detect GitHub token', async () => {
const result = await scanner.scan({
content: 'GITHUB_TOKEN=ghp_abcdefghijklmnopqrstuvwxyz01234'
});
expect(result.safe).toBe(false);
expect(result.issues.length).toBeGreaterThan(0);
const tokenIssue = result.issues.find(
issue => issue.type === 'secret'
);
expect(tokenIssue).toBeDefined();
expect(tokenIssue.severity).toBe('critical');
});
it('should detect AWS access key', async () => {
const result = await scanner.scan({
content: 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE'
});
expect(result.safe).toBe(false);
expect(result.issues.length).toBeGreaterThan(0);
});
it('should detect API key pattern', async () => {
const result = await scanner.scan({
content: 'api_key=generic_api_key_placeholder1234567890'
});
expect(result.safe).toBe(false);
});
it('should detect private key header', async () => {
const result = await scanner.scan({
content: '-----BEGIN RSA PRIVATE KEY-----'
});
expect(result.safe).toBe(false);
expect(result.issues.length).toBeGreaterThan(0);
});
it('should detect suspicious patterns', async () => {
const result = await scanner.scan({
content: 'eval(maliciousCode());'
});
expect(result.safe).toBe(false);
const evalIssue = result.issues.find(
issue => issue.type === 'suspicious_pattern'
);
expect(evalIssue).toBeDefined();
});
it('should detect document.write', async () => {
const result = await scanner.scan({
content: 'document.write("<script>evil()</script>");'
});
expect(result.safe).toBe(false);
});
it('should detect innerHTML assignment', async () => {
const result = await scanner.scan({
content: 'element.innerHTML = userInput;'
});
expect(result.safe).toBe(false);
});
it('should detect console.log', async () => {
const result = await scanner.scan({
content: 'console.log("debug info");'
});
expect(result.safe).toBe(true); // Low severity issues don't block
});
it('should scan multiple files', async () => {
const result = await scanner.scan({
files: [
{ path: 'file1.js', content: 'console.log("test");' },
{ path: 'file2.js', content: 'GITHUB_TOKEN=ghp_1234567890abcdefghijklmnopqrstuv' }
]
});
expect(result.safe).toBe(false);
expect(result.issues.length).toBeGreaterThan(0);
});
it('should detect high entropy strings', async () => {
const result = await scanner.scan({
content: 'SECRET_KEY=' + 'a'.repeat(40) + 'b'.repeat(20)
});
expect(result.safe).toBe(false);
});
});
describe('extractContent', () => {
it('should extract content from content field', () => {
const content = scanner.extractContent({
content: 'test content'
});
expect(content).toBe('test content');
});
it('should extract content from files array', () => {
const content = scanner.extractContent({
files: [
{ path: 'file1.js', content: 'content1' },
{ path: 'file2.js', content: 'content2' }
]
});
expect(content).toContain('content1');
expect(content).toContain('content2');
});
it('should return null for no content', () => {
const content = scanner.extractContent({});
expect(content).toBeNull();
});
});
describe('calculateEntropy', () => {
it('should calculate low entropy for simple text', () => {
const entropy = scanner.calculateEntropy('aaaa');
expect(entropy).toBe(0);
});
it('should calculate high entropy for random string', () => {
const entropy = scanner.calculateEntropy('aB3xY9zQ2w');
expect(entropy).toBeGreaterThan(2);
});
it('should handle empty string', () => {
const entropy = scanner.calculateEntropy('');
expect(entropy).toBe(0);
});
});
describe('isCommonPattern', () => {
it('should identify URL', () => {
expect(scanner.isCommonPattern('https://example.com')).toBe(true);
});
it('should identify file path', () => {
expect(scanner.isCommonPattern('/path/to/file.txt')).toBe(true);
});
it('should identify email', () => {
expect(scanner.isCommonPattern('user@example.com')).toBe(true);
});
it('should identify UUID', () => {
expect(scanner.isCommonPattern('550e8400-e29b-41d4-a716-446655440000')).toBe(true);
});
it('should not identify random string as common', () => {
expect(scanner.isCommonPattern('a'.repeat(32))).toBe(false);
});
});
describe('redactSecret', () => {
it('should redact long secrets', () => {
const redacted = scanner.redactSecret('verylongsecretkey1234567890');
expect(redacted).toBe('very****7890');
});
it('should redact short secrets', () => {
const redacted = scanner.redactSecret('short');
expect(redacted).toBe('****');
});
});
});
describe('Policy Enforcer', () => {
let enforcer;
beforeEach(() => {
enforcer = new PolicyEnforcer();
});
describe('check', () => {
it('should allow operation with no applicable policies', async () => {
const result = await enforcer.check('unknown_tool', {}, 'user123');
expect(result.allowed).toBe(true);
});
it('should enforce repository restrictions', async () => {
const result = await enforcer.check('create_commit', {
owner: 'test',
repo: 'protected-repo'
}, 'user123');
// Based on policy rules, this should check blocked patterns
expect(result).toHaveProperty('allowed');
});
it('should enforce branch restrictions', async () => {
const result = await enforcer.check('create_commit', {
owner: 'test',
repo: 'testrepo',
branch: 'main'
}, 'user123');
// Based on policy rules, 'main' should be blocked
expect(result).toHaveProperty('allowed');
});
});
describe('matchPattern', () => {
it('should match wildcard patterns', () => {
expect(enforcer.matchPattern('*', 'anything')).toBe(true);
expect(enforcer.matchPattern('test-*', 'test-123')).toBe(true);
expect(enforcer.matchPattern('test-*', 'not-test')).toBe(false);
});
it('should match exact patterns', () => {
expect(enforcer.matchPattern('exact', 'exact')).toBe(true);
expect(enforcer.matchPattern('exact', 'not-exact')).toBe(false);
});
});
describe('getFileExtension', () => {
it('should extract file extension', () => {
expect(enforcer.getFileExtension('test.js')).toBe('js');
expect(enforcer.getFileExtension('test.txt')).toBe('txt');
expect(enforcer.getFileExtension('test')).toBe('');
});
});
describe('policy management', () => {
it('should add policy', () => {
const initialCount = enforcer.getPolicies().length;
enforcer.addPolicy({
name: 'test-policy',
tools: ['test'],
conditions: {}
});
expect(enforcer.getPolicies().length).toBe(initialCount + 1);
});
it('should remove policy', () => {
enforcer.addPolicy({
name: 'policy-to-remove',
tools: ['test'],
conditions: {}
});
const initialCount = enforcer.getPolicies().length;
enforcer.removePolicy('policy-to-remove');
expect(enforcer.getPolicies().length).toBe(initialCount - 1);
});
});
});
describe('Dependency Checker', () => {
let checker;
beforeEach(() => {
checker = new DependencyChecker();
});
describe('extractDependencies', () => {
it('should parse package.json', () => {
const content = JSON.stringify({
dependencies: {
'express': '^4.18.0',
'lodash': '^4.17.21'
}
});
const deps = checker.parsePackageJson(content);
expect(deps).toHaveLength(2);
expect(deps[0].name).toBe('express');
expect(deps[0].ecosystem).toBe('npm');
});
it('should parse requirements.txt', () => {
const content = `
requests==2.28.0
flask>=2.0.0
`.trim();
const deps = checker.parseRequirementsTxt(content);
expect(deps.length).toBeGreaterThan(0);
expect(deps[0].ecosystem).toBe('PyPI');
});
it('should handle empty content', () => {
const deps = checker.parsePackageJson('{}');
expect(deps).toHaveLength(0);
});
});
describe('cleanVersion', () => {
it('should remove semver operators', () => {
expect(checker.cleanVersion('^4.18.0')).toBe('4.18.0');
expect(checker.cleanVersion('>=2.0.0')).toBe('2.0.0');
expect(checker.cleanVersion('~1.2.3')).toBe('1.2.3');
});
it('should handle wildcards', () => {
expect(checker.cleanVersion('*')).toBe('*');
});
});
describe('cache', () => {
it('should clear cache', () => {
checker.clearCache();
const stats = checker.getCacheStats();
expect(stats.size).toBe(0);
});
it('should get cache stats', () => {
const stats = checker.getCacheStats();
expect(stats).toHaveProperty('size');
expect(stats).toHaveProperty('ttl');
});
});
});