From 33cc1322cc881825cdfdb50bbf5d296dfbfb9fc2 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 27 Dec 2025 18:54:27 +0000 Subject: [PATCH] test: split security scanner coverage --- .../security-scanner.detection.test.ts | 234 ++++++++++++++++ .../security-scanner.reporting.test.ts | 29 ++ .../security/scanner/security-scanner.test.ts | 259 +----------------- 3 files changed, 265 insertions(+), 257 deletions(-) create mode 100644 frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.detection.test.ts create mode 100644 frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.reporting.test.ts diff --git a/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.detection.test.ts b/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.detection.test.ts new file mode 100644 index 000000000..a910bea9d --- /dev/null +++ b/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.detection.test.ts @@ -0,0 +1,234 @@ +import { describe, expect, it } from 'vitest' + +import { scanForVulnerabilities, securityScanner } from '@/lib/security-scanner' + +describe('security-scanner detection', () => { + describe('scanJavaScript', () => { + it.each([ + { + name: 'flag eval usage as critical', + code: ['const safe = true;', 'const result = eval("1 + 1")'].join('\n'), + expectedSeverity: 'critical', + expectedSafe: false, + expectedIssueType: 'dangerous', + expectedIssuePattern: 'eval', + expectedLine: 2, + }, + { + name: 'warn on localStorage usage but stay safe', + code: 'localStorage.setItem("k", "v")', + expectedSeverity: 'low', + expectedSafe: true, + expectedIssueType: 'warning', + expectedIssuePattern: 'localStorage', + }, + { + name: 'return safe for benign code', + code: 'const sum = (a, b) => a + b', + expectedSeverity: 'safe', + expectedSafe: true, + }, + ])( + 'should $name', + ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern, expectedLine }) => { + const result = securityScanner.scanJavaScript(code) + expect(result.severity).toBe(expectedSeverity) + expect(result.safe).toBe(expectedSafe) + + if (expectedIssueType || expectedIssuePattern) { + const issue = result.issues.find(item => { + const matchesType = expectedIssueType ? item.type === expectedIssueType : true + const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true + return matchesType && matchesPattern + }) + expect(issue).toBeDefined() + if (expectedLine !== undefined) { + expect(issue?.line).toBe(expectedLine) + } + } else { + expect(result.issues.length).toBe(0) + } + + if (expectedSafe) { + expect(result.sanitizedCode).toBe(code) + } else { + expect(result.sanitizedCode).toBeUndefined() + } + } + ) + }) + + describe('scanLua', () => { + it.each([ + { + name: 'flag os.execute usage as critical', + code: 'os.execute("rm -rf /")', + expectedSeverity: 'critical', + expectedSafe: false, + expectedIssueType: 'malicious', + expectedIssuePattern: 'os.execute', + }, + { + name: 'return safe for simple Lua function', + code: 'function add(a, b) return a + b end', + expectedSeverity: 'safe', + expectedSafe: true, + }, + ])('should $name', ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern }) => { + const result = securityScanner.scanLua(code) + expect(result.severity).toBe(expectedSeverity) + expect(result.safe).toBe(expectedSafe) + + if (expectedIssueType || expectedIssuePattern) { + const issue = result.issues.find(item => { + const matchesType = expectedIssueType ? item.type === expectedIssueType : true + const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true + return matchesType && matchesPattern + }) + expect(issue).toBeDefined() + } else { + expect(result.issues.length).toBe(0) + } + + if (expectedSafe) { + expect(result.sanitizedCode).toBe(code) + } else { + expect(result.sanitizedCode).toBeUndefined() + } + }) + }) + + describe('scanJSON', () => { + it.each([ + { + name: 'flag invalid JSON as medium severity', + json: '{"value": }', + expectedSeverity: 'medium', + expectedSafe: false, + expectedIssuePattern: 'JSON parse error', + }, + { + name: 'flag prototype pollution in JSON as critical', + json: '{"__proto__": {"polluted": true}}', + expectedSeverity: 'critical', + expectedSafe: false, + expectedIssuePattern: '__proto__', + }, + { + name: 'return safe for valid JSON', + json: '{"ok": true}', + expectedSeverity: 'safe', + expectedSafe: true, + }, + ])('should $name', ({ json, expectedSeverity, expectedSafe, expectedIssuePattern }) => { + const result = securityScanner.scanJSON(json) + expect(result.severity).toBe(expectedSeverity) + expect(result.safe).toBe(expectedSafe) + + if (expectedIssuePattern) { + expect(result.issues.some(issue => issue.pattern.includes(expectedIssuePattern))).toBe(true) + } else { + expect(result.issues.length).toBe(0) + } + + if (expectedSafe) { + expect(result.sanitizedCode).toBe(json) + } else { + expect(result.sanitizedCode).toBeUndefined() + } + }) + }) + + describe('scanHTML', () => { + it.each([ + { + name: 'flag script tags as critical', + html: '
', + expectedSeverity: 'critical', + expectedSafe: false, + }, + { + name: 'flag inline handlers as high', + html: '', + expectedSeverity: 'high', + expectedSafe: false, + }, + { + name: 'return safe for plain markup', + html: '
Safe
', + expectedSeverity: 'safe', + expectedSafe: true, + }, + ])('should $name', ({ html, expectedSeverity, expectedSafe }) => { + const result = securityScanner.scanHTML(html) + expect(result.severity).toBe(expectedSeverity) + expect(result.safe).toBe(expectedSafe) + }) + }) + + describe('sanitizeInput', () => { + it.each([ + { + name: 'remove script tags and inline handlers from text', + input: '
Click
x', + type: 'text' as const, + shouldExclude: ['', + type: 'html' as const, + shouldExclude: ['data:text/html', ' { + const sanitized = securityScanner.sanitizeInput(input, type) + shouldExclude.forEach(value => { + expect(sanitized).not.toContain(value) + }) + shouldInclude.forEach(value => { + expect(sanitized).toContain(value) + }) + }) + }) + + describe('scanForVulnerabilities', () => { + it.each([ + { + name: 'auto-detects JSON and flags prototype pollution', + code: '{"__proto__": {"polluted": true}}', + expectedSeverity: 'critical', + }, + { + name: 'auto-detects Lua when function/end present', + code: 'function dangerous() os.execute("rm -rf /") end', + expectedSeverity: 'critical', + }, + { + name: 'auto-detects HTML and flags script tags', + code: '
', + expectedSeverity: 'critical', + }, + { + name: 'falls back to JavaScript scanning', + code: 'const result = eval("1 + 1")', + expectedSeverity: 'critical', + }, + { + name: 'honors explicit type parameter', + code: 'return 1', + type: 'lua' as const, + expectedSeverity: 'safe', + }, + ])('should $name', ({ code, type, expectedSeverity }) => { + const result = scanForVulnerabilities(code, type) + expect(result.severity).toBe(expectedSeverity) + }) + }) +}) diff --git a/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.reporting.test.ts b/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.reporting.test.ts new file mode 100644 index 000000000..42f9a2dd2 --- /dev/null +++ b/frontends/nextjs/src/lib/security/scanner/__tests__/security-scanner.reporting.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' + +import { getSeverityColor, getSeverityIcon } from '@/lib/security-scanner' + +describe('security-scanner reporting', () => { + describe('getSeverityColor', () => { + it.each([ + { severity: 'critical', expected: 'error' }, + { severity: 'high', expected: 'warning' }, + { severity: 'medium', expected: 'info' }, + { severity: 'low', expected: 'secondary' }, + { severity: 'safe', expected: 'success' }, + ])('should map $severity to expected classes', ({ severity, expected }) => { + expect(getSeverityColor(severity)).toBe(expected) + }) + }) + + describe('getSeverityIcon', () => { + it.each([ + { severity: 'critical', expected: '\u{1F6A8}' }, + { severity: 'high', expected: '\u26A0\uFE0F' }, + { severity: 'medium', expected: '\u26A1' }, + { severity: 'low', expected: '\u2139\uFE0F' }, + { severity: 'safe', expected: '\u2713' }, + ])('should map $severity to expected icon', ({ severity, expected }) => { + expect(getSeverityIcon(severity)).toBe(expected) + }) + }) +}) diff --git a/frontends/nextjs/src/lib/security/scanner/security-scanner.test.ts b/frontends/nextjs/src/lib/security/scanner/security-scanner.test.ts index 142a8997b..0e9fcbd80 100644 --- a/frontends/nextjs/src/lib/security/scanner/security-scanner.test.ts +++ b/frontends/nextjs/src/lib/security/scanner/security-scanner.test.ts @@ -1,257 +1,2 @@ -import { describe, it, expect } from 'vitest' -import { securityScanner, scanForVulnerabilities, getSeverityColor, getSeverityIcon } from '@/lib/security-scanner' - -describe('security-scanner', () => { - describe('scanJavaScript', () => { - it.each([ - { - name: 'flag eval usage as critical', - code: ['const safe = true;', 'const result = eval("1 + 1")'].join('\n'), - expectedSeverity: 'critical', - expectedSafe: false, - expectedIssueType: 'dangerous', - expectedIssuePattern: 'eval', - expectedLine: 2, - }, - { - name: 'warn on localStorage usage but stay safe', - code: 'localStorage.setItem("k", "v")', - expectedSeverity: 'low', - expectedSafe: true, - expectedIssueType: 'warning', - expectedIssuePattern: 'localStorage', - }, - { - name: 'return safe for benign code', - code: 'const sum = (a, b) => a + b', - expectedSeverity: 'safe', - expectedSafe: true, - }, - ])( - 'should $name', - ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern, expectedLine }) => { - const result = securityScanner.scanJavaScript(code) - expect(result.severity).toBe(expectedSeverity) - expect(result.safe).toBe(expectedSafe) - - if (expectedIssueType || expectedIssuePattern) { - const issue = result.issues.find(item => { - const matchesType = expectedIssueType ? item.type === expectedIssueType : true - const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true - return matchesType && matchesPattern - }) - expect(issue).toBeDefined() - if (expectedLine !== undefined) { - expect(issue?.line).toBe(expectedLine) - } - } else { - expect(result.issues.length).toBe(0) - } - - if (expectedSafe) { - expect(result.sanitizedCode).toBe(code) - } else { - expect(result.sanitizedCode).toBeUndefined() - } - } - ) - }) - - describe('scanLua', () => { - it.each([ - { - name: 'flag os.execute usage as critical', - code: 'os.execute("rm -rf /")', - expectedSeverity: 'critical', - expectedSafe: false, - expectedIssueType: 'malicious', - expectedIssuePattern: 'os.execute', - }, - { - name: 'return safe for simple Lua function', - code: 'function add(a, b) return a + b end', - expectedSeverity: 'safe', - expectedSafe: true, - }, - ])('should $name', ({ code, expectedSeverity, expectedSafe, expectedIssueType, expectedIssuePattern }) => { - const result = securityScanner.scanLua(code) - expect(result.severity).toBe(expectedSeverity) - expect(result.safe).toBe(expectedSafe) - - if (expectedIssueType || expectedIssuePattern) { - const issue = result.issues.find(item => { - const matchesType = expectedIssueType ? item.type === expectedIssueType : true - const matchesPattern = expectedIssuePattern ? item.pattern.includes(expectedIssuePattern) : true - return matchesType && matchesPattern - }) - expect(issue).toBeDefined() - } else { - expect(result.issues.length).toBe(0) - } - - if (expectedSafe) { - expect(result.sanitizedCode).toBe(code) - } else { - expect(result.sanitizedCode).toBeUndefined() - } - }) - }) - - describe('scanJSON', () => { - it.each([ - { - name: 'flag invalid JSON as medium severity', - json: '{"value": }', - expectedSeverity: 'medium', - expectedSafe: false, - expectedIssuePattern: 'JSON parse error', - }, - { - name: 'flag prototype pollution in JSON as critical', - json: '{"__proto__": {"polluted": true}}', - expectedSeverity: 'critical', - expectedSafe: false, - expectedIssuePattern: '__proto__', - }, - { - name: 'return safe for valid JSON', - json: '{"ok": true}', - expectedSeverity: 'safe', - expectedSafe: true, - }, - ])('should $name', ({ json, expectedSeverity, expectedSafe, expectedIssuePattern }) => { - const result = securityScanner.scanJSON(json) - expect(result.severity).toBe(expectedSeverity) - expect(result.safe).toBe(expectedSafe) - - if (expectedIssuePattern) { - expect(result.issues.some(issue => issue.pattern.includes(expectedIssuePattern))).toBe(true) - } else { - expect(result.issues.length).toBe(0) - } - - if (expectedSafe) { - expect(result.sanitizedCode).toBe(json) - } else { - expect(result.sanitizedCode).toBeUndefined() - } - }) - }) - - describe('scanHTML', () => { - it.each([ - { - name: 'flag script tags as critical', - html: '
', - expectedSeverity: 'critical', - expectedSafe: false, - }, - { - name: 'flag inline handlers as high', - html: '', - expectedSeverity: 'high', - expectedSafe: false, - }, - { - name: 'return safe for plain markup', - html: '
Safe
', - expectedSeverity: 'safe', - expectedSafe: true, - }, - ])('should $name', ({ html, expectedSeverity, expectedSafe }) => { - const result = securityScanner.scanHTML(html) - expect(result.severity).toBe(expectedSeverity) - expect(result.safe).toBe(expectedSafe) - }) - }) - - describe('sanitizeInput', () => { - it.each([ - { - name: 'remove script tags and inline handlers from text', - input: '
Click
x', - type: 'text' as const, - shouldExclude: ['', - type: 'html' as const, - shouldExclude: ['data:text/html', ' { - const sanitized = securityScanner.sanitizeInput(input, type) - shouldExclude.forEach(value => { - expect(sanitized).not.toContain(value) - }) - shouldInclude.forEach(value => { - expect(sanitized).toContain(value) - }) - }) - }) - - describe('getSeverityColor', () => { - it.each([ - { severity: 'critical', expected: 'error' }, - { severity: 'high', expected: 'warning' }, - { severity: 'medium', expected: 'info' }, - { severity: 'low', expected: 'secondary' }, - { severity: 'safe', expected: 'success' }, - ])('should map $severity to expected classes', ({ severity, expected }) => { - expect(getSeverityColor(severity)).toBe(expected) - }) - }) - - describe('getSeverityIcon', () => { - it.each([ - { severity: 'critical', expected: '\u{1F6A8}' }, - { severity: 'high', expected: '\u26A0\uFE0F' }, - { severity: 'medium', expected: '\u26A1' }, - { severity: 'low', expected: '\u2139\uFE0F' }, - { severity: 'safe', expected: '\u2713' }, - ])('should map $severity to expected icon', ({ severity, expected }) => { - expect(getSeverityIcon(severity)).toBe(expected) - }) - }) - - describe('scanForVulnerabilities', () => { - it.each([ - { - name: 'auto-detects JSON and flags prototype pollution', - code: '{"__proto__": {"polluted": true}}', - expectedSeverity: 'critical', - }, - { - name: 'auto-detects Lua when function/end present', - code: 'function dangerous() os.execute("rm -rf /") end', - expectedSeverity: 'critical', - }, - { - name: 'auto-detects HTML and flags script tags', - code: '
', - expectedSeverity: 'critical', - }, - { - name: 'falls back to JavaScript scanning', - code: 'const result = eval("1 + 1")', - expectedSeverity: 'critical', - }, - { - name: 'honors explicit type parameter', - code: 'return 1', - type: 'lua' as const, - expectedSeverity: 'safe', - }, - ])('should $name', ({ code, type, expectedSeverity }) => { - const result = scanForVulnerabilities(code, type) - expect(result.severity).toBe(expectedSeverity) - }) - }) -}) +import './__tests__/security-scanner.detection.test' +import './__tests__/security-scanner.reporting.test'