UNPKG

@ordojs/security

Version:

Security package for OrdoJS with XSS, CSRF, and injection protection

380 lines (300 loc) 13.1 kB
import { execSync } from 'child_process'; import { existsSync, readFileSync } from 'fs'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { SecurityAuditOptions, SecurityAuditor } from './security-auditor'; // Mock file system and child_process vi.mock('fs'); vi.mock('child_process'); vi.mock('glob', () => ({ globSync: vi.fn(), })); const mockExistsSync = vi.mocked(existsSync); const mockReadFileSync = vi.mocked(readFileSync); const mockExecSync = vi.mocked(execSync); describe('SecurityAuditor', () => { let auditor: SecurityAuditor; let options: SecurityAuditOptions; beforeEach(() => { options = { projectPath: '/test/project', enableDependencyCheck: true, enableCodeAnalysis: true, enableConfigurationCheck: true, }; auditor = new SecurityAuditor(options); vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('audit', () => { it('should perform a complete security audit', async () => { // Mock package.json mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(JSON.stringify({ dependencies: { lodash: '^4.17.20' }, devDependencies: { axios: '^0.21.1' } })); // Mock npm audit mockExecSync.mockReturnValue(JSON.stringify({ vulnerabilities: { lodash: { severity: 'high', via: [{ source: 'CVE-2021-23337', title: 'Prototype Pollution', url: 'https://example.com' }] } } })); // Mock glob for source files const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/test.ts']); // Mock source file with vulnerability mockReadFileSync.mockImplementation((path: any) => { if (path.includes('package.json')) { return JSON.stringify({ dependencies: { lodash: '^4.17.20' }, devDependencies: { axios: '^0.21.1' } }); } if (path.includes('test.ts')) { return 'element.innerHTML = userInput; // XSS vulnerability'; } return ''; }); const result = await auditor.audit(); // The audit will find multiple vulnerabilities: // 1. npm audit vulnerability (lodash) // 2. known vulnerable packages (lodash, axios) // 3. XSS vulnerability in code // 4. configuration issues (no security headers, no HTTPS) expect(result.vulnerabilities.length).toBeGreaterThan(0); expect(result.summary.total).toBeGreaterThan(0); expect(result.owaspCompliance.score).toBeLessThan(100); expect(result.timestamp).toBeInstanceOf(Date); // Check that we have at least one dependency and one code vulnerability const hasDepVuln = result.vulnerabilities.some(v => v.type === 'dependency'); const hasCodeVuln = result.vulnerabilities.some(v => v.type === 'xss'); expect(hasDepVuln).toBe(true); expect(hasCodeVuln).toBe(true); }); it('should handle npm audit failures gracefully', async () => { mockExistsSync.mockImplementation((path: any) => { return path.includes('package.json'); }); mockReadFileSync.mockImplementation((path: any) => { if (path.includes('package.json')) { return JSON.stringify({ dependencies: { lodash: '^4.17.20' }, scripts: { dev: 'ordojs dev' } }); } return ''; }); // Mock npm audit failure mockExecSync.mockImplementation(() => { throw new Error('npm audit failed'); }); const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); const result = await auditor.audit(); // Should find known vulnerable packages and configuration issues expect(result.vulnerabilities.length).toBeGreaterThan(0); const hasDepVuln = result.vulnerabilities.some(v => v.type === 'dependency' && v.description.includes('lodash')); expect(hasDepVuln).toBe(true); }); }); describe('XSS vulnerability detection', () => { it('should detect innerHTML usage without sanitization', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/component.ts']); mockExistsSync.mockReturnValue(false); // No package.json mockReadFileSync.mockReturnValue('element.innerHTML = userInput;'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('xss'); expect(result.vulnerabilities[0].severity).toBe('high'); expect(result.vulnerabilities[0].description).toContain('innerHTML'); }); it('should detect eval usage', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/dangerous.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('eval(userCode);'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('xss'); expect(result.vulnerabilities[0].severity).toBe('critical'); expect(result.vulnerabilities[0].description).toContain('eval'); }); it('should detect unescaped template literals in HTML', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/template.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('const html = `<div>${userInput}</div>`;'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('xss'); expect(result.vulnerabilities[0].severity).toBe('medium'); }); }); describe('SQL injection detection', () => { it('should detect string concatenation in SQL queries', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/database.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('const query = "SELECT * FROM users WHERE id = " + userId;'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('injection'); expect(result.vulnerabilities[0].severity).toBe('high'); expect(result.vulnerabilities[0].description).toContain('SQL injection'); }); }); describe('CSRF vulnerability detection', () => { it('should detect forms without CSRF protection', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/form.html']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('<form method="post" action="/submit">'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('csrf'); expect(result.vulnerabilities[0].severity).toBe('medium'); }); }); describe('Cryptographic vulnerabilities', () => { it('should detect weak cryptographic algorithms', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/crypto.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('crypto.createHash("md5").update(data);'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('other'); expect(result.vulnerabilities[0].severity).toBe('medium'); expect(result.vulnerabilities[0].description).toContain('cryptographic'); }); }); describe('Hardcoded secrets detection', () => { it('should detect hardcoded passwords', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/config.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('const password = "secret123";'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('other'); expect(result.vulnerabilities[0].severity).toBe('high'); expect(result.vulnerabilities[0].description).toContain('Hardcoded secret'); }); it('should detect hardcoded API keys', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/api.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('const apiKey = "sk-1234567890abcdef";'); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].description).toContain('Hardcoded secret'); }); }); describe('Configuration audit', () => { it('should detect missing security headers configuration', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); mockExistsSync.mockImplementation((path: any) => { return path.includes('ordojs.config.ts'); }); mockReadFileSync.mockImplementation((path: any) => { if (path.includes('ordojs.config.ts')) { return 'export default { build: { outDir: "dist" } };'; } return ''; }); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('configuration'); expect(result.vulnerabilities[0].description).toContain('security headers'); }); it('should detect missing HTTPS configuration', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); mockExistsSync.mockImplementation((path: any) => { return path.includes('package.json'); }); mockReadFileSync.mockReturnValue(JSON.stringify({ scripts: { dev: 'ordojs dev', build: 'ordojs build' } })); const result = await auditor.audit(); expect(result.vulnerabilities).toHaveLength(1); expect(result.vulnerabilities[0].type).toBe('configuration'); expect(result.vulnerabilities[0].description).toContain('HTTPS'); }); }); describe('OWASP compliance', () => { it('should calculate OWASP compliance score', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); mockExistsSync.mockReturnValue(false); const result = await auditor.audit(); expect(result.owaspCompliance).toBeDefined(); expect(result.owaspCompliance.score).toBeGreaterThanOrEqual(0); expect(result.owaspCompliance.score).toBeLessThanOrEqual(100); expect(result.owaspCompliance.categories).toBeDefined(); }); it('should mark OWASP categories as non-compliant when vulnerabilities exist', async () => { const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue(['src/xss.ts']); mockExistsSync.mockReturnValue(false); mockReadFileSync.mockReturnValue('element.innerHTML = userInput;'); const result = await auditor.audit(); expect(result.owaspCompliance.categories['A03:2021 – Injection']).toBe(false); }); }); describe('options handling', () => { it('should respect enableDependencyCheck option', async () => { const auditorWithoutDeps = new SecurityAuditor({ ...options, enableDependencyCheck: false, }); const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); mockExistsSync.mockReturnValue(false); const result = await auditorWithoutDeps.audit(); expect(mockExecSync).not.toHaveBeenCalled(); expect(result.vulnerabilities).toHaveLength(0); }); it('should respect enableCodeAnalysis option', async () => { const auditorWithoutCode = new SecurityAuditor({ ...options, enableCodeAnalysis: false, }); mockExistsSync.mockReturnValue(false); const result = await auditorWithoutCode.audit(); const { globSync } = await import('glob'); expect(vi.mocked(globSync)).not.toHaveBeenCalled(); }); it('should respect custom include/exclude patterns', async () => { const customAuditor = new SecurityAuditor({ ...options, includePatterns: ['**/*.custom'], excludePatterns: ['**/ignore/**'], }); const { globSync } = await import('glob'); vi.mocked(globSync).mockReturnValue([]); mockExistsSync.mockReturnValue(false); await customAuditor.audit(); expect(vi.mocked(globSync)).toHaveBeenCalledWith( ['**/*.custom'], expect.objectContaining({ ignore: ['**/ignore/**'], }) ); }); }); });