vaultace-cli
Version:
AI-powered security scanner that detects vulnerabilities in AI-generated code. Proactive scanning, autonomous fixing, and emergency response for modern development teams.
526 lines (419 loc) • 16.8 kB
JavaScript
/**
* Regression Tests for Security Scanning Features
* Ensures security scanning accuracy and prevents feature regressions
*/
const { SecurityScanner } = require('../../src/utils/security-scanner');
const { VulnerabilityAnalyzer } = require('../../src/utils/vulnerability-analyzer');
const fs = require('fs').promises;
const path = require('path');
const crypto = require('crypto');
describe('Security Scanning Regression Tests', () => {
let scanner;
let analyzer;
let testFixturesPath;
beforeAll(async () => {
scanner = new SecurityScanner();
analyzer = new VulnerabilityAnalyzer();
testFixturesPath = path.join(__dirname, '../fixtures/security-scanning');
// Ensure test fixtures directory exists
await fs.mkdir(testFixturesPath, { recursive: true });
// Create test vulnerable files
await createTestFixtures();
});
afterAll(async () => {
await scanner.cleanup();
});
async function createTestFixtures() {
const fixtures = {
// JavaScript vulnerabilities
'vulnerable-js.js': `
// SQL Injection vulnerability
const query = "SELECT * FROM users WHERE id = " + userId;
// XSS vulnerability
document.innerHTML = userInput;
// Command injection
const exec = require('child_process').exec;
exec('ls ' + userInput);
// Hardcoded credentials
const apiKey = 'sk-1234567890abcdef';
const password = 'admin123';
`,
'package-vulnerable.json': JSON.stringify({
name: "test-vulnerable-app",
version: "1.0.0",
dependencies: {
"lodash": "4.17.0", // Known vulnerability
"axios": "0.18.0", // Known vulnerability
"express": "4.16.0", // Known vulnerability
"moment": "2.19.0" // Known vulnerability
}
}, null, 2),
// Python vulnerabilities
'vulnerable-python.py': `
query = "SELECT * FROM users WHERE name = '" + user_input + "'"
import os
os.system("ls " + user_input)
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "supersecret123"
import hashlib
hashlib.md5(password.encode()).hexdigest()
`,
// Docker vulnerabilities
'Dockerfile.vulnerable': `
FROM ubuntu:18.04
USER root
RUN apt-get update && apt-get install -y curl wget
COPY secret.key /app/
COPY .env /app/
EXPOSE 22
EXPOSE 3389
CMD ["./start.sh"]
`,
// Infrastructure as Code vulnerabilities
'terraform-vulnerable.tf': `
resource "aws_s3_bucket" "example" {
bucket = "my-vulnerable-bucket"
acl = "public-read"
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "example" {
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
storage_encrypted = false
publicly_accessible = true
}
`
};
for (const [filename, content] of Object.entries(fixtures)) {
await fs.writeFile(path.join(testFixturesPath, filename), content);
}
}
describe('Vulnerability Detection Accuracy', () => {
test('should detect JavaScript security vulnerabilities', async () => {
const filePath = path.join(testFixturesPath, 'vulnerable-js.js');
const results = await scanner.scanFile(filePath);
expect(results.vulnerabilities).toHaveLength(4);
// SQL Injection
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'sql_injection',
severity: 'high',
line: expect.any(Number),
description: expect.stringContaining('SQL injection')
})
);
// XSS
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'xss',
severity: 'high',
line: expect.any(Number)
})
);
// Command Injection
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'command_injection',
severity: 'critical',
line: expect.any(Number)
})
);
// Hardcoded Credentials
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'hardcoded_credentials',
severity: 'medium',
line: expect.any(Number)
})
);
});
test('should detect dependency vulnerabilities in package.json', async () => {
const filePath = path.join(testFixturesPath, 'package-vulnerable.json');
const results = await scanner.scanDependencies(filePath);
expect(results.vulnerabilities.length).toBeGreaterThan(0);
// Check for known vulnerable packages
const packageNames = results.vulnerabilities.map(v => v.package_name);
expect(packageNames).toContain('lodash');
expect(packageNames).toContain('axios');
expect(packageNames).toContain('express');
// Verify CVE information is included
results.vulnerabilities.forEach(vuln => {
expect(vuln.cve_id).toBeDefined();
expect(vuln.cvss_score).toBeGreaterThan(0);
expect(vuln.severity).toMatch(/^(low|medium|high|critical)$/);
});
});
test('should detect Python security issues', async () => {
const filePath = path.join(testFixturesPath, 'vulnerable-python.py');
const results = await scanner.scanFile(filePath);
expect(results.vulnerabilities.length).toBeGreaterThan(2);
// SQL Injection
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'sql_injection',
severity: 'high'
})
);
// Command Injection
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'command_injection',
severity: 'critical'
})
);
// Weak Cryptography
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'weak_cryptography',
severity: 'medium',
description: expect.stringContaining('MD5')
})
);
});
test('should detect Docker security misconfigurations', async () => {
const filePath = path.join(testFixturesPath, 'Dockerfile.vulnerable');
const results = await scanner.scanDockerfile(filePath);
expect(results.vulnerabilities.length).toBeGreaterThan(3);
// Running as root
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'docker_root_user',
severity: 'medium',
description: expect.stringContaining('root')
})
);
// Exposed sensitive ports
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'exposed_sensitive_port',
severity: 'high',
port: expect.any(Number)
})
);
});
test('should detect Infrastructure as Code vulnerabilities', async () => {
const filePath = path.join(testFixturesPath, 'terraform-vulnerable.tf');
const results = await scanner.scanTerraform(filePath);
expect(results.vulnerabilities.length).toBeGreaterThan(3);
// Public S3 bucket
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'public_s3_bucket',
severity: 'high',
resource: expect.stringContaining('s3_bucket')
})
);
// Overly permissive security group
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'overly_permissive_sg',
severity: 'critical',
resource: expect.stringContaining('security_group')
})
);
// Unencrypted database
expect(results.vulnerabilities).toContainEqual(
expect.objectContaining({
type: 'unencrypted_database',
severity: 'high',
resource: expect.stringContaining('db_instance')
})
);
});
});
describe('False Positive Prevention', () => {
test('should not flag secure code patterns as vulnerabilities', async () => {
const secureCode = `
// Secure SQL query with parameterization
const query = "SELECT * FROM users WHERE id = ?";
const results = await db.execute(query, [userId]);
// Secure HTML output with escaping
const safeHTML = escapeHtml(userInput);
element.textContent = safeHTML;
// Secure command execution with validation
const allowedCommands = ['ls', 'pwd', 'whoami'];
if (allowedCommands.includes(command)) {
exec(command);
}
`;
const tempFile = path.join(testFixturesPath, 'secure-code.js');
await fs.writeFile(tempFile, secureCode);
const results = await scanner.scanFile(tempFile);
// Should have very few or no vulnerabilities
expect(results.vulnerabilities.length).toBeLessThan(2);
// Should not flag parameterized queries as SQL injection
const sqlInjectionVulns = results.vulnerabilities.filter(v => v.type === 'sql_injection');
expect(sqlInjectionVulns).toHaveLength(0);
await fs.unlink(tempFile);
});
test('should distinguish between actual secrets and test data', async () => {
const testCode = `
// Test data - should not be flagged as secrets
const testApiKey = 'test-api-key-123';
const mockPassword = 'mock-password';
const exampleToken = 'example-token-xyz';
// Constants that look like secrets but aren't
const API_ENDPOINT = 'https://api.example.com';
const DEFAULT_TIMEOUT = 30000;
`;
const tempFile = path.join(testFixturesPath, 'test-code.js');
await fs.writeFile(tempFile, testCode);
const results = await scanner.scanFile(tempFile);
// Should not flag test/mock data as real secrets
const secretVulns = results.vulnerabilities.filter(v => v.type === 'hardcoded_credentials');
expect(secretVulns.length).toBeLessThan(2);
await fs.unlink(tempFile);
});
});
describe('Performance Regression Tests', () => {
test('should scan large files within acceptable time limits', async () => {
// Create a large JavaScript file
const largeCode = Array.from({ length: 10000 }, (_, i) =>
`function func${i}() { console.log("Function ${i}"); }`
).join('\n');
const largeFile = path.join(testFixturesPath, 'large-file.js');
await fs.writeFile(largeFile, largeCode);
const startTime = Date.now();
const results = await scanner.scanFile(largeFile);
const endTime = Date.now();
const scanTime = endTime - startTime;
// Should complete within 5 seconds for large files
expect(scanTime).toBeLessThan(5000);
expect(results).toBeDefined();
expect(results.scan_time_ms).toBeLessThan(5000);
await fs.unlink(largeFile);
});
test('should handle concurrent scans efficiently', async () => {
const testFiles = [];
// Create multiple test files
for (let i = 0; i < 10; i++) {
const fileName = `concurrent-test-${i}.js`;
const filePath = path.join(testFixturesPath, fileName);
const content = `
const vulnerability${i} = "SELECT * FROM table WHERE id = " + userInput${i};
const secret${i} = "api-key-${i}-123456789";
`;
await fs.writeFile(filePath, content);
testFiles.push(filePath);
}
const startTime = Date.now();
// Scan all files concurrently
const scanPromises = testFiles.map(filePath => scanner.scanFile(filePath));
const results = await Promise.all(scanPromises);
const endTime = Date.now();
const totalTime = endTime - startTime;
// Should complete concurrent scans efficiently
expect(totalTime).toBeLessThan(10000); // 10 seconds max
expect(results).toHaveLength(10);
// Each result should contain vulnerabilities
results.forEach(result => {
expect(result.vulnerabilities.length).toBeGreaterThan(0);
});
// Cleanup
await Promise.all(testFiles.map(file => fs.unlink(file)));
});
});
describe('Scan Result Consistency', () => {
test('should produce consistent results across multiple scans', async () => {
const filePath = path.join(testFixturesPath, 'vulnerable-js.js');
// Run the same scan multiple times
const results1 = await scanner.scanFile(filePath);
const results2 = await scanner.scanFile(filePath);
const results3 = await scanner.scanFile(filePath);
// Results should be identical
expect(results1.vulnerabilities).toHaveLength(results2.vulnerabilities.length);
expect(results2.vulnerabilities).toHaveLength(results3.vulnerabilities.length);
// Vulnerability types and locations should match
const sortVulns = (vulns) => vulns.sort((a, b) => a.line - b.line);
const sorted1 = sortVulns([...results1.vulnerabilities]);
const sorted2 = sortVulns([...results2.vulnerabilities]);
const sorted3 = sortVulns([...results3.vulnerabilities]);
for (let i = 0; i < sorted1.length; i++) {
expect(sorted1[i].type).toBe(sorted2[i].type);
expect(sorted2[i].type).toBe(sorted3[i].type);
expect(sorted1[i].line).toBe(sorted2[i].line);
expect(sorted2[i].line).toBe(sorted3[i].line);
}
});
test('should maintain scan result format and structure', async () => {
const filePath = path.join(testFixturesPath, 'vulnerable-js.js');
const results = await scanner.scanFile(filePath);
// Verify expected result structure
expect(results).toHaveProperty('vulnerabilities');
expect(results).toHaveProperty('scan_metadata');
expect(results).toHaveProperty('file_info');
// Verify vulnerability structure
results.vulnerabilities.forEach(vuln => {
expect(vuln).toHaveProperty('type');
expect(vuln).toHaveProperty('severity');
expect(vuln).toHaveProperty('line');
expect(vuln).toHaveProperty('description');
expect(vuln).toHaveProperty('recommendation');
// Severity should be valid
expect(['low', 'medium', 'high', 'critical']).toContain(vuln.severity);
// Line number should be positive
expect(vuln.line).toBeGreaterThan(0);
});
// Verify scan metadata
expect(results.scan_metadata).toHaveProperty('scan_time_ms');
expect(results.scan_metadata).toHaveProperty('scanner_version');
expect(results.scan_metadata).toHaveProperty('timestamp');
});
});
describe('Error Handling and Edge Cases', () => {
test('should gracefully handle missing files', async () => {
const nonExistentFile = path.join(testFixturesPath, 'does-not-exist.js');
await expect(scanner.scanFile(nonExistentFile)).rejects.toThrow(/file not found/i);
});
test('should handle empty files without errors', async () => {
const emptyFile = path.join(testFixturesPath, 'empty.js');
await fs.writeFile(emptyFile, '');
const results = await scanner.scanFile(emptyFile);
expect(results).toBeDefined();
expect(results.vulnerabilities).toHaveLength(0);
expect(results.scan_metadata).toBeDefined();
await fs.unlink(emptyFile);
});
test('should handle files with invalid syntax', async () => {
const invalidFile = path.join(testFixturesPath, 'invalid-syntax.js');
await fs.writeFile(invalidFile, 'const invalid = { unclosed object');
const results = await scanner.scanFile(invalidFile);
// Should still attempt to scan even with syntax errors
expect(results).toBeDefined();
expect(results.vulnerabilities).toBeDefined();
await fs.unlink(invalidFile);
});
test('should handle very large files gracefully', async () => {
const hugeContent = 'const data = "' + 'x'.repeat(1000000) + '";';
const hugeFile = path.join(testFixturesPath, 'huge-file.js');
await fs.writeFile(hugeFile, hugeContent);
const results = await scanner.scanFile(hugeFile);
expect(results).toBeDefined();
expect(results.file_info.size_bytes).toBeGreaterThan(1000000);
await fs.unlink(hugeFile);
});
});
});