mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
test: split security scanner coverage
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user