Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/llms-txt-hub / packages/validators/src/validate-security.ts |
| Date | Not specified in source |
| Repository | Janady13/llms-txt-hub |
| Revision | e0f4bf978d4df168369c7601d9c5f5ea89e84377 |
| Branch | main |
| Path or archive member | packages/validators/src/validate-security.ts |
| Original SHA-256 | dafb1af5f173e23778685edeb6b81af4e3fd3299b03dd940725c6fc2f32cfd94 |
| Git blob SHA | bad07899d4472efecb2ba1cc9c05d665087acbee |
| Reading-copy SHA-256 | dafb1af5f173e23778685edeb6b81af4e3fd3299b03dd940725c6fc2f32cfd94 |
Recovered source transcript
Download complete source text ↓236 lines · 6.9 KB
#!/usr/bin/env tsx
/**
* Security Validator
*
* Ensures:
* - No hardcoded secrets
* - Proper environment variable usage
* - No unsafe operations
* - SQL injection prevention
* - XSS prevention
*/
import fs from 'node:fs'
import chalk from 'chalk'
interface SecurityViolation {
file: string
line: number
message: string
severity: 'error' | 'warning' | 'critical'
type: string
}
const SECRET_PATTERNS = [
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{20,}/i, type: 'API Key' },
{ pattern: /secret[_-]?key\s*[:=]\s*['"][^'"]{20,}/i, type: 'Secret Key' },
{ pattern: /password\s*[:=]\s*['"][^'"]+/i, type: 'Password' },
{ pattern: /token\s*[:=]\s*['"][^'"]{20,}/i, type: 'Token' },
{ pattern: /private[_-]?key\s*[:=]\s*['"][^'"]+/i, type: 'Private Key' },
{ pattern: /aws[_-]?access[_-]?key/i, type: 'AWS Key' },
{ pattern: /github[_-]?token/i, type: 'GitHub Token' },
{ pattern: /stripe[_-]?key/i, type: 'Stripe Key' }
]
const UNSAFE_PATTERNS = [
{ pattern: /eval\s*\(/, message: 'Avoid using eval()', severity: 'critical' as const },
{
pattern: /innerHTML\s*=/,
message: 'Use textContent instead of innerHTML',
severity: 'warning' as const
},
{
pattern: /dangerouslySetInnerHTML/,
message: 'Avoid dangerouslySetInnerHTML when possible',
severity: 'warning' as const
},
{ pattern: /document\.write/, message: 'Avoid document.write', severity: 'error' as const },
{
pattern: /new Function\(/,
message: 'Avoid creating functions from strings',
severity: 'critical' as const
}
]
const SQL_INJECTION_PATTERNS = [
{
pattern: /query\s*\(\s*['"`].*\$\{/,
message: 'Potential SQL injection - use parameterized queries'
},
{
pattern: /execute\s*\(\s*['"`].*\+/,
message: 'Potential SQL injection - avoid string concatenation in queries'
}
]
class SecurityValidator {
private violations: SecurityViolation[] = []
validateFile(filePath: string): void {
const content = fs.readFileSync(filePath, 'utf-8')
const lines = content.split('\n')
lines.forEach((line, index) => {
this.checkHardcodedSecrets(filePath, line, index + 1)
this.checkUnsafeOperations(filePath, line, index + 1)
this.checkSQLInjection(filePath, line, index + 1)
this.checkXSSVulnerabilities(filePath, line, index + 1)
this.checkEnvironmentVariables(filePath, line, index + 1)
})
}
private checkHardcodedSecrets(file: string, line: string, lineNumber: number): void {
// Skip comments and test files
if (line.trim().startsWith('//') || line.trim().startsWith('*')) return
if (file.includes('.test.') || file.includes('.spec.')) return
for (const { pattern, type } of SECRET_PATTERNS) {
if (pattern.test(line)) {
// Check if it's using environment variable
if (!line.includes('process.env') && !line.includes('import.meta.env')) {
this.violations.push({
file,
line: lineNumber,
message: `Potential hardcoded ${type} detected`,
severity: 'critical',
type: 'secret'
})
}
}
}
}
private checkUnsafeOperations(file: string, line: string, lineNumber: number): void {
for (const { pattern, message, severity } of UNSAFE_PATTERNS) {
if (pattern.test(line)) {
this.violations.push({
file,
line: lineNumber,
message,
severity,
type: 'unsafe'
})
}
}
}
private checkSQLInjection(file: string, line: string, lineNumber: number): void {
for (const { pattern, message } of SQL_INJECTION_PATTERNS) {
if (pattern.test(line)) {
this.violations.push({
file,
line: lineNumber,
message,
severity: 'critical',
type: 'sql-injection'
})
}
}
}
private checkXSSVulnerabilities(file: string, line: string, lineNumber: number): void {
// Check for unescaped user input
if (line.includes('req.query') || line.includes('req.params') || line.includes('req.body')) {
if (line.includes('innerHTML') || line.includes('document.write')) {
this.violations.push({
file,
line: lineNumber,
message: 'Potential XSS - user input used in unsafe context',
severity: 'critical',
type: 'xss'
})
}
}
// Check for missing input validation
if (line.includes('parseInt') && !line.includes('Number.isNaN')) {
this.violations.push({
file,
line: lineNumber,
message: 'Validate parseInt results with Number.isNaN',
severity: 'warning',
type: 'validation'
})
}
}
private checkEnvironmentVariables(file: string, line: string, lineNumber: number): void {
// Check for direct process.env access without validation
const envPattern = /process\.env\.(\w+)/g
const matches = line.matchAll(envPattern)
for (const match of matches) {
const envVar = match[1]
// Check if it's validated
if (!line.includes('||') && !line.includes('??') && !line.includes('if')) {
this.violations.push({
file,
line: lineNumber,
message: `Environment variable ${envVar} used without fallback`,
severity: 'warning',
type: 'env'
})
}
}
}
report(): boolean {
if (this.violations.length === 0) {
console.log(chalk.green('✓ No security issues found'))
return true
}
const critical = this.violations.filter(v => v.severity === 'critical')
const errors = this.violations.filter(v => v.severity === 'error')
const warnings = this.violations.filter(v => v.severity === 'warning')
if (critical.length > 0) {
console.log(chalk.red.bold(`\n🚨 Found ${critical.length} CRITICAL security issues:`))
critical.forEach(v => {
console.log(chalk.red.bold(` ${v.file}:${v.line} - ${v.message}`))
})
}
if (errors.length > 0) {
console.log(chalk.red(`\n✗ Found ${errors.length} security errors:`))
errors.forEach(v => {
console.log(chalk.red(` ${v.file}:${v.line} - ${v.message}`))
})
}
if (warnings.length > 0) {
console.log(chalk.yellow(`\n⚠ Found ${warnings.length} security warnings:`))
warnings.forEach(v => {
console.log(chalk.yellow(` ${v.file}:${v.line} - ${v.message}`))
})
}
return critical.length === 0 && errors.length === 0
}
}
export function validateSecurity(files: string[]): boolean {
const validator = new SecurityValidator()
for (const file of files) {
if (!file.match(/\.(ts|tsx|js|jsx)$/)) continue
if (file.includes('node_modules')) continue
try {
validator.validateFile(file)
} catch (error) {
console.error(chalk.red(`Error processing ${file}:`), error)
}
}
return validator.report()
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2)
const success = validateSecurity(args)
process.exit(success ? 0 : 1)
}