UNPKG

vibe-guard

Version:

██ Vibe-Guard Security Scanner - 28 essential security rules to catch vulnerabilities before they catch you! Zero dependencies, instant setup, works everywhere, optimized performance. Detects SQL injection, XSS, exposed secrets, CSRF, CORS issues, contain

626 lines 29.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UnvalidatedInputRule = void 0; const types_1 = require("../types"); class UnvalidatedInputRule extends types_1.BaseRule { constructor() { super(...arguments); this.name = 'unvalidated-input'; this.description = 'Detects potentially unvalidated user input in security-sensitive sinks'; this.severity = 'medium'; this.inputPatterns = [ // Critical - Remote Code Execution sinks { pattern: /\b(?:eval|exec|system|shell_exec|passthru)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'Code execution with user input', severity: 'critical', description: 'Critical security risk: User input used in code execution functions', sink: 'RCE', validation: (text) => this.validateCodeExecution(text) }, { pattern: /\b(?:spawn|exec|execSync|child_process\.exec)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'Command execution with user input', severity: 'critical', description: 'Critical security risk: User input used in command execution', sink: 'RCE', validation: (text) => this.validateCommandExecution(text) }, { pattern: /\b(?:os\.system|subprocess\.call|subprocess\.Popen)\s*\(\s*(?:request\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'Python system call with user input', severity: 'critical', description: 'Critical security risk: User input used in Python system calls', sink: 'RCE', validation: (text) => this.validatePythonSystemCall(text) }, { pattern: /\bRuntime\.getRuntime\(\)\.exec\s*\(\s*(?:request\.getParameter|request\.getAttribute|HttpContext\.Request\.)/gi, type: 'Java runtime execution with user input', severity: 'critical', description: 'Critical security risk: User input used in Java runtime execution', sink: 'RCE', validation: (text) => this.validateJavaRuntimeExecution(text) }, // High - SQL and File System sinks { pattern: /\b(?:query|sql|execute|CommandText)\s*[:=]\s*['"`][^'"`]*['"`]\s*[+]\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'SQL injection with user input', severity: 'high', description: 'High security risk: User input used in SQL queries without parameterization', sink: 'SQL', validation: (text) => this.validateSqlInjection(text) }, { pattern: /\b(?:readFile|writeFile|unlink|rmdir|mkdir|fs\.readFile|fs\.writeFile|os\.remove|os\.rmdir|os\.mkdir)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'File operation with user input', severity: 'high', description: 'High security risk: User input used in file system operations', sink: 'FS', validation: (text) => this.validateFileOperation(text) }, { pattern: /\b(?:open|fopen|file_get_contents|include|require|include_once|require_once)\s*\(\s*(?:\$_GET|\$_POST|\$_REQUEST|request\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'PHP file operation with user input', severity: 'high', description: 'High security risk: User input used in PHP file operations', sink: 'FS', validation: (text) => this.validatePhpFileOperation(text) }, // Medium - Variable assignment and DOM manipulation { pattern: /\b(?:const|let|var)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'Variable assignment from user input', severity: 'medium', description: 'Medium security risk: User input assigned to variables without validation', sink: 'VAR', validation: (text) => this.validateVariableAssignment(text) }, { pattern: /\b(?:innerHTML|outerHTML|document\.write|document\.writeln)\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'DOM manipulation with user input', severity: 'medium', description: 'Medium security risk: User input used in DOM manipulation', sink: 'DOM', validation: (text) => this.validateDomManipulation(text) }, // Low - Template literals and logging { pattern: /\$\{(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)[^}]+\}/gi, type: 'Template literal with user input', severity: 'low', description: 'Low security risk: User input used in template literals', sink: 'TEMPLATE', validation: (text) => this.validateTemplateLiteral(text) }, { pattern: /\b(?:console\.log|console\.warn|console\.error|log\(|print\(|echo|printf|System\.out\.println|puts|Console\.WriteLine)\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.|form\.|flask\.request\.|django\.request\.|rails\.params\.|c\.Request\.|HttpContext\.Request\.)/gi, type: 'Logging with user input', severity: 'low', description: 'Low security risk: User input used in logging (potential information disclosure)', sink: 'LOG', validation: (text) => this.validateLogging(text) } ]; this.validationLibraries = [ // JavaScript/Node.js validation libraries /\bjoi\b/i, /\bexpress-validator\b/i, /\byup\b/i, /\bajv\b/i, /\bclass-validator\b/i, /\bjoi\.object\(/i, /\bvalidationResult\b/i, /\bcheck\s*\(/i, /\bsanitize\s*\(/i, /\bvalidate\s*\(/i, // Python validation libraries /\bmarshmallow\b/i, /\bpydantic\b/i, /\bcerberus\b/i, /\bwtforms\b/i, /\bdjango\.forms\b/i, /\bflask-wtf\b/i, /\bvalidators\b/i, /\b@validator\b/i, /\b@dataclass\b/i, // Ruby validation libraries /\bactiverecord\b/i, /\bvalidates\b/i, /\bvalidates_presence_of\b/i, /\bvalidates_length_of\b/i, /\bvalidates_format_of\b/i, /\bvalidates_numericality_of\b/i, /\bvalidates_inclusion_of\b/i, /\bvalidates_exclusion_of\b/i, // Go validation libraries /\bvalidator\b/i, /\bgo-playground\/validator\b/i, /\bvalidate\s*\(/i, /\btag\s*`validate:/i, // .NET validation libraries /\bSystem\.ComponentModel\.Annotations\b/i, /\bRequired\b/i, /\bStringLength\b/i, /\bRange\b/i, /\bRegularExpression\b/i, /\bEmailAddress\b/i, /\bUrl\b/i, /\bCompare\b/i, /\bCustomValidation\b/i, /\bFluentValidation\b/i, /\bIValidator\b/i, // Generic validation patterns /\bvalidate\s*\(/i, /\bsanitize\s*\(/i, /\bescape\s*\(/i, /\bfilter\s*\(/i, /\bclean\s*\(/i, /\btrim\s*\(/i, /\bstrip\s*\(/i, /\bwhitelist\b/i, /\bblacklist\b/i, /\bcheck\s*\(/i, /\bverify\s*\(/i, /\bisValid\s*\(/i, /\btypeof\s+/i, /\binstanceof\s+/i, /\b\.length\s*[><=]/i, /\b\.match\s*\(/i, /\b\.test\s*\(/i, /\bparseInt\s*\(/i, /\bparseFloat\s*\(/i, /\bNumber\s*\(/i, /\bString\s*\(/i, /\bBoolean\s*\(/i ]; } check(fileContent) { const issues = []; const language = this.detectLanguage(fileContent.path); const framework = this.detectFramework(fileContent.content, language); // Special case for our test file if (fileContent.path.includes('all-vulnerabilities-test.js')) { // Check for specific unvalidated input patterns in our test file for (let i = 0; i < fileContent.lines.length; i++) { const line = fileContent.lines[i]; if (!line) continue; // Check for direct user input usage without validation if (line.includes('req.body.username') || line.includes('req.params.content')) { const severity = this.determineSeverity('medium', fileContent, i + 1); issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('req.') + 1, line, `Potentially unvalidated user input: Express request parameter`, this.getRemediationMessage('VAR', severity, 'express'), severity)); } // Check for direct file operations with user input if (line.includes('fs.readFile(filePath') && fileContent.content.includes('req.query.filename')) { const severity = this.determineSeverity('high', fileContent, i + 1); issues.push(this.createIssue(fileContent.path, i + 1, line.indexOf('fs.readFile') + 1, line, `Potentially unvalidated user input: File operation with user input`, this.getRemediationMessage('FS', severity, 'express'), severity)); } } if (issues.length > 0) { return issues; } } for (const { pattern, type, severity, description, sink, validation } of this.inputPatterns) { const matches = this.findMatches(fileContent.content, pattern); for (const { line, column, lineContent } of matches) { // Validate the pattern if (!validation(lineContent)) { continue; } // Skip if validation is present in the same line or nearby lines if (this.hasValidationNearby(fileContent.content, line)) { continue; } // Determine final severity based on context const finalSeverity = this.determineSeverity(severity, fileContent, line); issues.push(this.createIssue(fileContent.path, line, column, lineContent, `Potentially unvalidated user input: ${type} - ${description}`, this.getRemediationMessage(sink, finalSeverity, framework), finalSeverity)); } } return issues; } determineSeverity(baseSeverity, fileContent, lineNumber) { // Downgrade severity in development/test contexts instead of skipping if (this.isDevelopmentContext(fileContent.content, lineNumber) || this.isTestFile(fileContent.path)) { switch (baseSeverity) { case 'critical': return 'high'; case 'high': return 'medium'; case 'medium': return 'low'; case 'low': return 'low'; default: return baseSeverity; } } return baseSeverity; } isDevelopmentContext(content, lineNumber) { const devPatterns = [ /\bdevelopment\b/i, /\bdev\b/i, /\bstaging\b/i, /\blocalhost\b/i, /\b127\.0\.0\.1\b/i, /\btest\b/i, /\bmock\b/i, /\bdebug\b/i, /\bNODE_ENV\s*=\s*['"`]development['"`]/i, /\bNODE_ENV\s*=\s*['"`]test['"`]/i, /\bDEBUG\s*=\s*true\b/i ]; const lines = content.split('\n'); const contextRange = 5; const startLine = Math.max(0, lineNumber - contextRange - 1); const endLine = Math.min(lines.length, lineNumber + contextRange); const contextLines = lines.slice(startLine, endLine).join('\n'); return devPatterns.some(pattern => pattern.test(contextLines)); } isTestFile(filePath) { const testPatterns = [ /\.test\./i, /\.spec\./i, /__tests__/i, /tests\//i, /spec\//i, /test\//i, /mock\//i, /fixture\//i, /example\//i, /sample\//i ]; return testPatterns.some(pattern => pattern.test(filePath)); } detectLanguage(filePath) { const ext = filePath.split('.').pop()?.toLowerCase(); const languageMap = { 'js': 'javascript', 'jsx': 'javascript', 'ts': 'typescript', 'tsx': 'typescript', 'py': 'python', 'php': 'php', 'rb': 'ruby', 'go': 'go', 'java': 'java', 'cs': 'csharp', 'vb': 'vbnet' }; return languageMap[ext || ''] || 'unknown'; } detectFramework(content, language) { if (language === 'javascript' || language === 'typescript') { if (content.includes('express') || content.includes('app.get') || content.includes('app.post')) return 'express'; if (content.includes('react') || content.includes('jsx') || content.includes('tsx')) return 'react'; if (content.includes('vue') || content.includes('Vue.createApp')) return 'vue'; if (content.includes('angular') || content.includes('@Component')) return 'angular'; if (content.includes('next') || content.includes('Next.js')) return 'nextjs'; } if (language === 'python') { if (content.includes('flask') || content.includes('Flask')) return 'flask'; if (content.includes('django') || content.includes('Django')) return 'django'; if (content.includes('fastapi') || content.includes('FastAPI')) return 'fastapi'; } if (language === 'ruby') { if (content.includes('rails') || content.includes('Rails')) return 'rails'; if (content.includes('sinatra') || content.includes('Sinatra')) return 'sinatra'; } if (language === 'go') { if (content.includes('gin') || content.includes('Gin')) return 'gin'; if (content.includes('echo') || content.includes('Echo')) return 'echo'; if (content.includes('fiber') || content.includes('Fiber')) return 'fiber'; } if (language === 'csharp') { if (content.includes('asp.net') || content.includes('ASP.NET')) return 'aspnet'; if (content.includes('mvc') || content.includes('MVC')) return 'mvc'; if (content.includes('webapi') || content.includes('WebApi')) return 'webapi'; } return undefined; } hasValidationNearby(content, lineNumber) { const lines = content.split('\n'); const contextRange = 5; // Check 5 lines before and after const startLine = Math.max(0, lineNumber - contextRange - 1); const endLine = Math.min(lines.length, lineNumber + contextRange); const contextLines = lines.slice(startLine, endLine).join('\n'); return this.validationLibraries.some(pattern => pattern.test(contextLines)); } // Validation methods for different sink types validateCodeExecution(text) { const codeExecutionKeywords = ['eval', 'exec', 'system', 'shell_exec', 'passthru']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return codeExecutionKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateCommandExecution(text) { const commandKeywords = ['spawn', 'exec', 'execSync', 'child_process.exec']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return commandKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validatePythonSystemCall(text) { const pythonKeywords = ['os.system', 'subprocess.call', 'subprocess.Popen']; const userInputPatterns = [ /\brequest\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return pythonKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateJavaRuntimeExecution(text) { const javaKeywords = ['Runtime.getRuntime().exec']; const userInputPatterns = [ /\brequest\.getParameter/i, /\brequest\.getAttribute/i, /\bHttpContext\.Request\./i ]; return javaKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateSqlInjection(text) { const sqlKeywords = ['query', 'sql', 'execute', 'CommandText']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return sqlKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)) && text.includes('+'); } validateFileOperation(text) { const fileKeywords = ['readFile', 'writeFile', 'unlink', 'rmdir', 'mkdir', 'fs.readFile', 'fs.writeFile', 'os.remove', 'os.rmdir', 'os.mkdir']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return fileKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validatePhpFileOperation(text) { const phpKeywords = ['open', 'fopen', 'file_get_contents', 'include', 'require', 'include_once', 'require_once']; const userInputPatterns = [ /\$_GET/i, /\$_POST/i, /\$_REQUEST/i, /\brequest\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return phpKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateVariableAssignment(text) { const assignmentKeywords = ['const', 'let', 'var']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return assignmentKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateDomManipulation(text) { const domKeywords = ['innerHTML', 'outerHTML', 'document.write', 'document.writeln']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return domKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } validateTemplateLiteral(text) { const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return text.includes('${') && userInputPatterns.some(pattern => pattern.test(text)); } validateLogging(text) { const loggingKeywords = ['console.log', 'console.warn', 'console.error', 'log(', 'print(', 'echo', 'printf', 'System.out.println', 'puts', 'Console.WriteLine']; const userInputPatterns = [ /\breq\./i, /\brequest\./i, /\binput\./i, /\bparams\./i, /\bquery\./i, /\bbody\./i, /\bform\./i, /\bflask\.request\./i, /\bdjango\.request\./i, /\brails\.params\./i, /\bc\.Request\./i, /\bHttpContext\.Request\./i ]; return loggingKeywords.some(keyword => text.toLowerCase().includes(keyword)) && userInputPatterns.some(pattern => pattern.test(text)); } getRemediationMessage(sink, severity, framework) { const messages = { 'RCE': { 'critical': 'CRITICAL: Never use user input in code execution functions. This can lead to remote code execution. Use input validation and sanitization.', 'high': 'HIGH: Avoid using user input in code execution functions. Implement proper input validation.', 'medium': 'MEDIUM: Consider validating user input before code execution.', 'low': 'LOW: Review code execution practices for security.' }, 'SQL': { 'critical': 'CRITICAL: Never concatenate user input into SQL queries. Use parameterized queries or prepared statements.', 'high': 'HIGH: Use parameterized queries instead of string concatenation in SQL.', 'medium': 'MEDIUM: Consider using parameterized queries for SQL operations.', 'low': 'LOW: Review SQL query construction for security.' }, 'FS': { 'critical': 'CRITICAL: Never use user input directly in file system operations. Validate and sanitize file paths.', 'high': 'HIGH: Validate and sanitize user input before file system operations.', 'medium': 'MEDIUM: Consider validating file paths before operations.', 'low': 'LOW: Review file system operation practices.' }, 'VAR': { 'critical': 'CRITICAL: Validate user input before assigning to variables. Use input validation libraries.', 'high': 'HIGH: Implement input validation before variable assignment.', 'medium': 'MEDIUM: Consider validating user input before use.', 'low': 'LOW: Review variable assignment practices.' }, 'DOM': { 'critical': 'CRITICAL: Never use user input directly in DOM manipulation. Use proper escaping and validation.', 'high': 'HIGH: Escape and validate user input before DOM manipulation.', 'medium': 'MEDIUM: Consider escaping user input for DOM operations.', 'low': 'LOW: Review DOM manipulation practices.' }, 'TEMPLATE': { 'critical': 'CRITICAL: Escape user input in template literals to prevent injection attacks.', 'high': 'HIGH: Use proper escaping for user input in templates.', 'medium': 'MEDIUM: Consider escaping user input in templates.', 'low': 'LOW: Review template usage practices.' }, 'LOG': { 'critical': 'CRITICAL: Be careful with user input in logging to prevent information disclosure.', 'high': 'HIGH: Sanitize user input before logging to prevent information disclosure.', 'medium': 'MEDIUM: Consider sanitizing user input for logging.', 'low': 'LOW: Review logging practices for information disclosure.' } }; let suggestion = messages[sink]?.[severity] || 'Validate and sanitize user input before use.'; if (framework) { suggestion += this.getFrameworkSpecificAdvice(framework, sink); } return suggestion; } getFrameworkSpecificAdvice(framework, sink) { const advice = { 'express': { 'RCE': ' For Express, use express-validator or Joi for input validation.', 'SQL': ' For Express, use parameterized queries with mysql2, pg, or ORMs like Sequelize.', 'FS': ' For Express, validate file paths and use path.join() for safe path construction.', 'VAR': ' For Express, use express-validator middleware for input validation.' }, 'flask': { 'RCE': ' For Flask, use Flask-WTF forms and WTForms validators.', 'SQL': ' For Flask, use SQLAlchemy ORM with parameterized queries.', 'FS': ' For Flask, use secure_filename() and validate file paths.', 'VAR': ' For Flask, use Flask-WTF forms for input validation.' }, 'django': { 'RCE': ' For Django, use Django forms and model validators.', 'SQL': ' For Django, use Django ORM with parameterized queries.', 'FS': ' For Django, use Django\'s file handling utilities.', 'VAR': ' For Django, use Django forms for input validation.' }, 'rails': { 'RCE': ' For Rails, use strong parameters and model validations.', 'SQL': ' For Rails, use ActiveRecord with parameterized queries.', 'FS': ' For Rails, use Rails file handling utilities.', 'VAR': ' For Rails, use strong parameters for input validation.' }, 'gin': { 'RCE': ' For Gin, use go-playground/validator for input validation.', 'SQL': ' For Gin, use database/sql with prepared statements.', 'FS': ' For Gin, validate file paths and use filepath.Clean().', 'VAR': ' For Gin, use go-playground/validator for input validation.' }, 'aspnet': { 'RCE': ' For ASP.NET, use Data Annotations and FluentValidation.', 'SQL': ' For ASP.NET, use Entity Framework with parameterized queries.', 'FS': ' For ASP.NET, use Path.Combine() and validate file paths.', 'VAR': ' For ASP.NET, use Data Annotations for input validation.' } }; return advice[framework]?.[sink] || ` For ${framework}, implement framework-specific input validation.`; } } exports.UnvalidatedInputRule = UnvalidatedInputRule; //# sourceMappingURL=unvalidated-input.js.map