security.ts•12.5 kB
import { SecurityCheck, SecurityIssue } from '../types/index.js';
export class SecurityService {
static checkCode(code: string, language: string): SecurityCheck {
const issues: SecurityIssue[] = [];
// Common security patterns across languages
const commonChecks = [
this.checkHardcodedSecrets(code),
this.checkSqlInjection(code),
this.checkXssVulnerabilities(code),
this.checkCommandInjection(code),
this.checkInsecureRandomness(code),
];
// Language-specific checks
switch (language.toLowerCase()) {
case 'javascript':
case 'typescript':
commonChecks.push(
this.checkEvalUsage(code),
this.checkInnerHtml(code),
this.checkUnsafeRegex(code)
);
break;
case 'python':
commonChecks.push(
this.checkPythonEval(code),
this.checkPickleUsage(code),
this.checkShellCommand(code)
);
break;
case 'java':
commonChecks.push(
this.checkJavaDeserialization(code),
this.checkRuntimeExec(code)
);
break;
}
// Flatten and filter issues
const allIssues = commonChecks.flat().filter(Boolean) as SecurityIssue[];
return {
passed: allIssues.length === 0,
issues: allIssues,
recommendations: this.generateRecommendations(allIssues),
};
}
static sanitizeCode(code: string): string {
// Remove or mask sensitive information
let sanitized = code;
// Remove API keys and tokens
sanitized = sanitized.replace(/['"](sk-[a-zA-Z0-9]{32,})['"]/g, '"<API_KEY_MASKED>"');
sanitized = sanitized.replace(/['"](ghp_[a-zA-Z0-9]{36})['"]/g, '"<GITHUB_TOKEN_MASKED>"');
sanitized = sanitized.replace(/['"](xoxb-[a-zA-Z0-9-]{51,})['"]/g, '"<SLACK_TOKEN_MASKED>"');
// Remove database connection strings
sanitized = sanitized.replace(/['"](postgresql:\/\/[^'"]+)['"]/g, '"<DATABASE_URL_MASKED>"');
sanitized = sanitized.replace(/['"](mysql:\/\/[^'"]+)['"]/g, '"<DATABASE_URL_MASKED>"');
sanitized = sanitized.replace(/['"](mongodb:\/\/[^'"]+)['"]/g, '"<DATABASE_URL_MASKED>"');
// Remove email addresses
sanitized = sanitized.replace(/['"]([\w\.-]+@[\w\.-]+\.\w+)['"]/g, '"<EMAIL_MASKED>"');
// Remove potential passwords
sanitized = sanitized.replace(/password\s*[:=]\s*['"](.*?)['"]/gi, 'password: "<PASSWORD_MASKED>"');
sanitized = sanitized.replace(/secret\s*[:=]\s*['"](.*?)['"]/gi, 'secret: "<SECRET_MASKED>"');
return sanitized;
}
static isPathSafe(path: string): boolean {
// Check for directory traversal
if (path.includes('..')) return false;
// Check for absolute paths
if (path.startsWith('/')) return false;
// Check for dangerous characters
if (/[<>:"|?*]/.test(path)) return false;
// Check for null bytes
if (path.includes('\0')) return false;
return true;
}
static validateApiAccess(url: string): boolean {
// Only allow GitHub API access
return url.startsWith('https://api.github.com/') || url.startsWith('https://github.com/');
}
static rateLimit(key: string, limit: number = 100, window: number = 60000): boolean {
// Simple in-memory rate limiting
const now = Date.now();
const windowStart = now - window;
if (!this.rateLimitStore) {
this.rateLimitStore = new Map();
}
const requests = this.rateLimitStore.get(key) || [];
const validRequests = requests.filter((time: number) => time > windowStart);
if (validRequests.length >= limit) {
return false;
}
validRequests.push(now);
this.rateLimitStore.set(key, validRequests);
return true;
}
private static rateLimitStore: Map<string, number[]>;
private static checkHardcodedSecrets(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
{ pattern: /['"](sk-[a-zA-Z0-9]{32,})['"]/, name: 'OpenAI API Key' },
{ pattern: /['"](ghp_[a-zA-Z0-9]{36})['"]/, name: 'GitHub Personal Access Token' },
{ pattern: /['"](xoxb-[a-zA-Z0-9-]{51,})['"]/, name: 'Slack Bot Token' },
{ pattern: /['"](AIza[0-9A-Za-z-_]{35})['"]/, name: 'Google API Key' },
{ pattern: /['"](AKIA[0-9A-Z]{16})['"]/, name: 'AWS Access Key' },
{ pattern: /['"](ya29\.[0-9A-Za-z\-_]+)['"]/, name: 'Google OAuth Token' },
];
for (const { pattern, name } of patterns) {
const matches = code.match(pattern);
if (matches) {
issues.push({
type: 'high',
description: `Hardcoded ${name} found in code`,
file: 'current',
suggestion: `Store ${name} in environment variables or secure configuration`,
});
}
}
return issues;
}
private static checkSqlInjection(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/query\s*\(\s*['"]\s*SELECT\s.*?\+.*?['"]\s*\)/i,
/execute\s*\(\s*['"]\s*SELECT\s.*?\+.*?['"]\s*\)/i,
/sql\s*=\s*['"]\s*SELECT\s.*?\+.*?['"]/i,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'high',
description: 'Potential SQL injection vulnerability',
file: 'current',
suggestion: 'Use parameterized queries or prepared statements',
});
}
}
return issues;
}
private static checkXssVulnerabilities(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/dangerouslySetInnerHTML/,
/innerHTML\s*=\s*.*?\+/,
/document\.write\s*\(/,
/eval\s*\(/,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'medium',
description: 'Potential XSS vulnerability',
file: 'current',
suggestion: 'Sanitize user input and use safe DOM manipulation methods',
});
}
}
return issues;
}
private static checkCommandInjection(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/exec\s*\(\s*.*?\+.*?\)/,
/system\s*\(\s*.*?\+.*?\)/,
/spawn\s*\(\s*.*?\+.*?\)/,
/execSync\s*\(\s*.*?\+.*?\)/,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'high',
description: 'Potential command injection vulnerability',
file: 'current',
suggestion: 'Validate and sanitize input before passing to system commands',
});
}
}
return issues;
}
private static checkInsecureRandomness(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/Math\.random\(\)/,
/random\.random\(\)/,
/rand\(\)/,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'medium',
description: 'Insecure random number generation',
file: 'current',
suggestion: 'Use cryptographically secure random number generators',
});
}
}
return issues;
}
private static checkEvalUsage(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
if (/eval\s*\(/.test(code)) {
issues.push({
type: 'high',
description: 'Use of eval() function',
file: 'current',
suggestion: 'Avoid eval() and use safer alternatives like JSON.parse() or Function constructor',
});
}
return issues;
}
private static checkInnerHtml(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
if (/innerHTML\s*=/.test(code)) {
issues.push({
type: 'medium',
description: 'Direct use of innerHTML',
file: 'current',
suggestion: 'Use textContent or sanitize HTML content',
});
}
return issues;
}
private static checkUnsafeRegex(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
// Check for potential ReDoS patterns
const redosPatterns = [
/\(\.\*\)\+/,
/\(\.\+\)\+/,
/\(\.\*\)\*/,
/\(\.\+\)\*/,
];
for (const pattern of redosPatterns) {
if (pattern.test(code)) {
issues.push({
type: 'medium',
description: 'Potential ReDoS (Regular Expression Denial of Service) vulnerability',
file: 'current',
suggestion: 'Review regex patterns for potential catastrophic backtracking',
});
}
}
return issues;
}
private static checkPythonEval(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/eval\s*\(/,
/exec\s*\(/,
/compile\s*\(/,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'high',
description: 'Use of dangerous Python eval/exec functions',
file: 'current',
suggestion: 'Use safer alternatives like ast.literal_eval() or avoid dynamic code execution',
});
}
}
return issues;
}
private static checkPickleUsage(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
if (/pickle\.loads?\s*\(/.test(code)) {
issues.push({
type: 'high',
description: 'Use of pickle.load() or pickle.loads()',
file: 'current',
suggestion: 'Avoid pickle for untrusted data, use JSON or other safe serialization formats',
});
}
return issues;
}
private static checkShellCommand(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const patterns = [
/subprocess\.call\s*\(\s*.*shell\s*=\s*True/,
/os\.system\s*\(/,
/subprocess\.run\s*\(\s*.*shell\s*=\s*True/,
];
for (const pattern of patterns) {
if (pattern.test(code)) {
issues.push({
type: 'high',
description: 'Use of shell commands with potential injection risk',
file: 'current',
suggestion: 'Use subprocess without shell=True or validate input thoroughly',
});
}
}
return issues;
}
private static checkJavaDeserialization(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
if (/ObjectInputStream|readObject\s*\(/.test(code)) {
issues.push({
type: 'high',
description: 'Java deserialization vulnerability',
file: 'current',
suggestion: 'Validate serialized data or use safer serialization methods',
});
}
return issues;
}
private static checkRuntimeExec(code: string): SecurityIssue[] {
const issues: SecurityIssue[] = [];
if (/Runtime\.getRuntime\(\)\.exec\s*\(/.test(code)) {
issues.push({
type: 'high',
description: 'Use of Runtime.exec() for command execution',
file: 'current',
suggestion: 'Use ProcessBuilder with proper input validation',
});
}
return issues;
}
private static generateRecommendations(issues: SecurityIssue[]): string[] {
const recommendations: string[] = [];
if (issues.length === 0) {
recommendations.push('No security issues detected');
return recommendations;
}
const highIssues = issues.filter(issue => issue.type === 'high');
const mediumIssues = issues.filter(issue => issue.type === 'medium');
const lowIssues = issues.filter(issue => issue.type === 'low');
if (highIssues.length > 0) {
recommendations.push(`Address ${highIssues.length} high-priority security issues immediately`);
}
if (mediumIssues.length > 0) {
recommendations.push(`Review ${mediumIssues.length} medium-priority security issues`);
}
if (lowIssues.length > 0) {
recommendations.push(`Consider addressing ${lowIssues.length} low-priority security issues`);
}
recommendations.push('Implement security linting in your CI/CD pipeline');
recommendations.push('Regular security audits and dependency updates');
recommendations.push('Use environment variables for sensitive configuration');
return recommendations;
}
}