mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
#!/usr/bin/env tsx
|
|
|
|
import { readdirSync, readFileSync, statSync } from 'fs'
|
|
import { join, extname } from 'path'
|
|
|
|
interface SecurityIssue {
|
|
severity: 'critical' | 'high' | 'medium' | 'low'
|
|
file: string
|
|
line: number
|
|
issue: string
|
|
pattern: string
|
|
}
|
|
|
|
const SECURITY_PATTERNS = [
|
|
{
|
|
name: 'eval usage',
|
|
pattern: /eval\s*\(/g,
|
|
severity: 'critical' as const,
|
|
description: 'eval() can execute arbitrary code'
|
|
},
|
|
{
|
|
name: 'innerHTML assignment',
|
|
pattern: /\.innerHTML\s*=/g,
|
|
severity: 'high' as const,
|
|
description: 'innerHTML is vulnerable to XSS. Use textContent or sanitize input'
|
|
},
|
|
{
|
|
name: 'dangerouslySetInnerHTML',
|
|
pattern: /dangerouslySetInnerHTML/g,
|
|
severity: 'high' as const,
|
|
description: 'Ensure the content is properly sanitized'
|
|
},
|
|
{
|
|
name: 'hardcoded credentials',
|
|
pattern: /(password|secret|token|apiKey|api_key)\s*[:=]\s*['"][\w-]+['"]/gi,
|
|
severity: 'critical' as const,
|
|
description: 'Potential hardcoded credentials detected'
|
|
},
|
|
{
|
|
name: 'SQL injection risk',
|
|
pattern: /\$\{.*\}|`.*\+.*`/g,
|
|
severity: 'high' as const,
|
|
description: 'Potential SQL injection via string interpolation'
|
|
},
|
|
{
|
|
name: 'unvalidated fetch',
|
|
pattern: /fetch\s*\(\s*[^'"]/g,
|
|
severity: 'medium' as const,
|
|
description: 'Fetch with non-string URL could be unsafe'
|
|
},
|
|
{
|
|
name: 'missing input validation',
|
|
pattern: /\.value|\.text(?!Content)/g,
|
|
severity: 'medium' as const,
|
|
description: 'User input should be validated'
|
|
},
|
|
{
|
|
name: 'missing CORS headers',
|
|
pattern: /(res\.|response\.).*header|setHeader/g,
|
|
severity: 'medium' as const,
|
|
description: 'Consider CORS security headers'
|
|
}
|
|
]
|
|
|
|
function scanFiles(): SecurityIssue[] {
|
|
const issues: SecurityIssue[] = []
|
|
const srcDir = 'src'
|
|
|
|
function walkDir(dir: string) {
|
|
try {
|
|
const files = readdirSync(dir)
|
|
|
|
for (const file of files) {
|
|
const fullPath = join(dir, file)
|
|
const stat = statSync(fullPath)
|
|
|
|
if (stat.isDirectory() && !['node_modules', '.next', 'dist', 'build'].includes(file)) {
|
|
walkDir(fullPath)
|
|
} else if (['.ts', '.tsx', '.js', '.jsx'].includes(extname(file))) {
|
|
scanFile(fullPath, issues)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Skip inaccessible directories
|
|
}
|
|
}
|
|
|
|
walkDir(srcDir)
|
|
return issues
|
|
}
|
|
|
|
function scanFile(filePath: string, issues: SecurityIssue[]) {
|
|
try {
|
|
const content = readFileSync(filePath, 'utf8')
|
|
const lines = content.split('\n')
|
|
|
|
for (const patternConfig of SECURITY_PATTERNS) {
|
|
let match
|
|
const pattern = new RegExp(patternConfig.pattern.source, 'g')
|
|
|
|
while ((match = pattern.exec(content)) !== null) {
|
|
const lineNumber = content.substring(0, match.index).split('\n').length
|
|
|
|
// Skip if in comment
|
|
const line = lines[lineNumber - 1]
|
|
const commentStart = line.indexOf('//')
|
|
const matchPosition = match.index - content.substring(0, match.index).lastIndexOf('\n') - 1
|
|
|
|
if (commentStart >= 0 && matchPosition > commentStart) {
|
|
continue
|
|
}
|
|
|
|
issues.push({
|
|
severity: patternConfig.severity,
|
|
file: filePath,
|
|
line: lineNumber,
|
|
issue: patternConfig.description,
|
|
pattern: patternConfig.name
|
|
})
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Skip files that can't be read
|
|
}
|
|
}
|
|
|
|
// Main execution
|
|
const issues = scanFiles()
|
|
|
|
const summary = {
|
|
totalIssues: issues.length,
|
|
critical: issues.filter(i => i.severity === 'critical').length,
|
|
high: issues.filter(i => i.severity === 'high').length,
|
|
medium: issues.filter(i => i.severity === 'medium').length,
|
|
low: issues.filter(i => i.severity === 'low').length,
|
|
issues: issues.sort((a, b) => {
|
|
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }
|
|
return severityOrder[a.severity] - severityOrder[b.severity]
|
|
}),
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
|
|
console.log(JSON.stringify(summary, null, 2))
|
|
|
|
// Exit with error if critical issues found
|
|
if (summary.critical > 0) {
|
|
process.exit(1)
|
|
}
|