test: split security scanner coverage

This commit is contained in:
2025-12-27 18:54:27 +00:00
parent 99d4411a41
commit 33cc1322cc
3 changed files with 265 additions and 257 deletions

View File

@@ -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: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
expectedSafe: false,
},
{
name: 'flag inline handlers as high',
html: '<button onclick="alert(1)">Click</button>',
expectedSeverity: 'high',
expectedSafe: false,
},
{
name: 'return safe for plain markup',
html: '<div><span>Safe</span></div>',
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: '<div onclick="alert(1)">Click</div><script>alert(2)</script><a href="javascript:alert(3)">x</a>',
type: 'text' as const,
shouldExclude: ['<script', 'onclick', 'javascript:'],
},
{
name: 'remove data html URIs from html',
input: '<img src="data:text/html;base64,abc"><script>alert(1)</script>',
type: 'html' as const,
shouldExclude: ['data:text/html', '<script'],
},
{
name: 'neutralize prototype pollution in json',
input: '{"__proto__": {"polluted": true}, "note": "constructor[\\"prototype\\"]"}',
type: 'json' as const,
shouldInclude: ['_proto_'],
shouldExclude: ['__proto__', 'constructor["prototype"]'],
},
])('should $name', ({ input, type, shouldExclude = [], shouldInclude = [] }) => {
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: '<div><script>alert(1)</script></div>',
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)
})
})
})

View File

@@ -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)
})
})
})

View File

@@ -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: '<div><script>alert(1)</script></div>',
expectedSeverity: 'critical',
expectedSafe: false,
},
{
name: 'flag inline handlers as high',
html: '<button onclick="alert(1)">Click</button>',
expectedSeverity: 'high',
expectedSafe: false,
},
{
name: 'return safe for plain markup',
html: '<div><span>Safe</span></div>',
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: '<div onclick="alert(1)">Click</div><script>alert(2)</script><a href="javascript:alert(3)">x</a>',
type: 'text' as const,
shouldExclude: ['<script', 'onclick', 'javascript:'],
},
{
name: 'remove data html URIs from html',
input: '<img src="data:text/html;base64,abc"><script>alert(1)</script>',
type: 'html' as const,
shouldExclude: ['data:text/html', '<script'],
},
{
name: 'neutralize prototype pollution in json',
input: '{"__proto__": {"polluted": true}, "note": "constructor[\\"prototype\\"]"}',
type: 'json' as const,
shouldInclude: ['_proto_'],
shouldExclude: ['__proto__', 'constructor["prototype"]'],
},
])('should $name', ({ input, type, shouldExclude = [], shouldInclude = [] }) => {
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: '<div><script>alert(1)</script></div>',
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'