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

631 lines 30.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BrokenAccessControlRule = void 0; const types_1 = require("../types"); class BrokenAccessControlRule extends types_1.BaseRule { constructor() { super(...arguments); this.name = 'broken-access-control'; this.description = 'Detects missing authorization checks and insecure direct object references with context-aware analysis'; this.severity = 'high'; this.accessControlPatterns = [ // Missing authorization checks in routes: Tighter patterns { pattern: /app\.(?:get|post|put|delete|patch)\s*\(\s*['"`][^'"`]*\/(?:admin|user|api|dashboard|settings|profile|account|billing|payment|order)[^'"`]*['"`]\s*,\s*(?!.*(?:auth|login|verify|middleware|authorize|permission|guard|protect))/gi, type: 'Protected route without authorization', confidence: 0.9, severity: 'high', validation: (text) => this.validateProtectedRoute(text) }, // Direct object references without ownership checks: Tighter patterns { pattern: /findById\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^)]*\)/gi, type: 'Direct object reference without ownership check', confidence: 0.85, severity: 'high', validation: (text) => this.validateDirectObjectReference(text) }, { pattern: /findOne\s*\(\s*\{[^}]*id\s*:\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^}]*\}/gi, type: 'Database query without ownership check', confidence: 0.8, severity: 'high', validation: (text) => this.validateDatabaseQuery(text) }, { pattern: /find\s*\(\s*\{[^}]*_id\s*:\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^}]*\}/gi, type: 'MongoDB query without ownership check', confidence: 0.8, severity: 'high', validation: (text) => this.validateMongoDBQuery(text) }, { pattern: /where\s*\(\s*['"`]id['"`]\s*,\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)/gi, type: 'ORM query without ownership check', confidence: 0.8, severity: 'high', validation: (text) => this.validateORMQuery(text) }, // File access without authorization: Tighter patterns { pattern: /readFile\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^)]*\)/gi, type: 'File access without authorization', confidence: 0.9, severity: 'critical', validation: (text) => this.validateFileAccess(text) }, { pattern: /writeFile\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^)]*\)/gi, type: 'File write without authorization', confidence: 0.95, severity: 'critical', validation: (text) => this.validateFileWrite(text) }, { pattern: /unlink\s*\(\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^)]*\)/gi, type: 'File deletion without authorization', confidence: 0.95, severity: 'critical', validation: (text) => this.validateFileDeletion(text) }, // Database operations without user context: Tighter patterns { pattern: /\.update\s*\(\s*\{[^}]*\},\s*\{[^}]*id\s*:\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^}]*\}/gi, type: 'Database update without user context', confidence: 0.85, severity: 'high', validation: (text) => this.validateDatabaseUpdate(text) }, { pattern: /\.delete\s*\(\s*\{[^}]*id\s*:\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^}]*\}/gi, type: 'Database deletion without user context', confidence: 0.9, severity: 'critical', validation: (text) => this.validateDatabaseDeletion(text) }, { pattern: /\.remove\s*\(\s*\{[^}]*_id\s*:\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)[^}]*\}/gi, type: 'MongoDB removal without user context', confidence: 0.9, severity: 'critical', validation: (text) => this.validateMongoDBRemoval(text) }, // Role-based access control missing: Tighter patterns { pattern: /(?:admin|user|role)\s*[:=]\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)/gi, type: 'Role assignment from user input', confidence: 0.9, severity: 'critical', validation: (text) => this.validateRoleAssignment(text) }, { pattern: /(?:permission|access)\s*[:=]\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)/gi, type: 'Permission assignment from user input', confidence: 0.9, severity: 'critical', validation: (text) => this.validatePermissionAssignment(text) }, // Session manipulation: Tighter patterns { pattern: /req\.session\.(?:user|role|admin)\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)/gi, type: 'Session manipulation with user input', confidence: 0.95, severity: 'critical', validation: (text) => this.validateSessionManipulation(text) }, { pattern: /session\[(?:user|role|admin)\]\s*=\s*(?:req\.|request\.|input\.|params\.|query\.|body\.)/gi, type: 'Session assignment with user input', confidence: 0.95, severity: 'critical', validation: (text) => this.validateSessionAssignment(text) }, // PHP patterns: Tighter patterns { pattern: /\$_SESSION\[(?:user|role|admin)\]\s*=\s*(?:\$_GET|\$_POST|\$_REQUEST)/gi, type: 'PHP session manipulation with user input', confidence: 0.95, severity: 'critical', validation: (text) => this.validatePHPSessionManipulation(text) }, { pattern: /SELECT\s+\*\s+FROM\s+\w+\s+WHERE\s+id\s*=\s*(?:\$_GET|\$_POST|\$_REQUEST)/gi, type: 'PHP database query without authorization', confidence: 0.9, severity: 'high', validation: (text) => this.validatePHPDatabaseQuery(text) }, // Python patterns: Tighter patterns { pattern: /session\[(?:user|role|admin)\]\s*=\s*(?:request\.|flask\.request\.)/gi, type: 'Python session manipulation with user input', confidence: 0.95, severity: 'critical', validation: (text) => this.validatePythonSessionManipulation(text) }, { pattern: /User\.query\.filter_by\(id\s*=\s*(?:request\.|flask\.request\.)/gi, type: 'Python ORM query without authorization', confidence: 0.9, severity: 'high', validation: (text) => this.validatePythonORMQuery(text) }, // Java patterns: Tighter patterns { pattern: /session\.setAttribute\s*\(\s*['"`](?:user|role|admin)['"`]\s*,\s*(?:request\.getParameter|request\.getAttribute)/gi, type: 'Java session manipulation with user input', confidence: 0.95, severity: 'critical', validation: (text) => this.validateJavaSessionManipulation(text) }, { pattern: /userRepository\.findById\s*\(\s*(?:request\.getParameter|request\.getAttribute)/gi, type: 'Java repository query without authorization', confidence: 0.9, severity: 'high', validation: (text) => this.validateJavaRepositoryQuery(text) } ]; // Multi line comment patterns! this.multiLineCommentPatterns = [ /\/\*[\s\S]*?\*\//g, // JavaScript/TypeScript multi-line comments /""".*?"""/gs, // Python docstrings /<!--.*?-->/gs, // HTML comments /#\[\[.*?\]\]/gs, // Lua multi-line comments /\/\*[\s\S]*?\*\//g, // C/C++ multi-line comments /\/\*[\s\S]*?\*\//g // Java multi-line comments ]; this.authorizationPatterns = [ // Specific authorization check patterns: Only match actual middleware/auth calls /auth\s*\(/i, /authorize\s*\(/i, /permission\s*\(/i, /role\s*\(/i, /admin\s*\(/i, /user\s*\(/i, /owner\s*\(/i, /belongsTo\s*\(/i, /canAccess\s*\(/i, /hasPermission\s*\(/i, /isAuthorized\s*\(/i, /checkAccess\s*\(/i, /validateAccess\s*\(/i, /verifyOwnership\s*\(/i, /ensureOwnership\s*\(/i, /middleware\s*\(/i, /guard\s*\(/i, /protect\s*\(/i, /secure\s*\(/i, /authorized\s*\(/i, /authenticated\s*\(/i, /loggedIn\s*\(/i, /session\s*\(/i, /token\s*\(/i, /jwt\s*\(/i ]; this.falsePositivePatterns = [ // Development and testing patterns: /example/i, /demo/i, /test/i, /sample/i, /placeholder/i, /development/i, /dev/i, /staging/i, /localhost/i, /127\.0\.0\.1/i, /console\.log/i, /console\.warn/i, /console\.error/i, /logger\.(?:log|warn|error|info)/i, /print/i, /echo/i, /printf/i, /System\.out\.println/i, /puts/i, /Console\.WriteLine/i, /comment/i, /note/i, /todo/i, /fixme/i, // Documentation and examples: /documentation/i, /docs?/i, /readme/i, /example[_-]?code/i, /sample[_-]?code/i, /demo[_-]?code/i, /tutorial/i, /guide/i, // Test files and directories: /test[_-]?files?/i, /test[_-]?data/i, /test[_-]?cases/i, /spec[_-]?files?/i, /__tests__/i, /\.test\./i, /\.spec\./i, // Configuration and setup: /config[_-]?example/i, /setup[_-]?example/i, /template[_-]?example/i ]; } check(fileContent) { const issues = []; const language = this.detectLanguage(fileContent.path); const framework = this.detectFramework(fileContent.content, language); const hasAuthorizationChecks = this.hasAuthorizationChecks(fileContent.content); const hasAuthentication = this.hasAuthentication(fileContent.content); const isProtectedRoute = this.isProtectedRoute(fileContent.content); for (const { pattern, type, confidence, severity, validation } of this.accessControlPatterns) { const matches = this.findMatches(fileContent.content, pattern); for (const { match, line, column, lineContent } of matches) { const matchedText = match[0]; const context = this.analyzeContext(fileContent, line, column, language, framework, hasAuthorizationChecks, hasAuthentication, isProtectedRoute, type); // Skips if in safe context if (this.isSafeContext(context)) { continue; } // Validates the access control issue if (!validation(matchedText)) { continue; } // Calculates final confidence and severity based on context const finalConfidence = this.calculateConfidence(confidence, context); const finalSeverity = this.calculateSeverity(severity, context); if (finalConfidence >= 0.5) { issues.push(this.createIssue(fileContent.path, line, column, lineContent, `${finalSeverity.toUpperCase()}: ${type} detected (confidence: ${Math.round(finalConfidence * 100)}%): ${this.getLineContext(lineContent, column)}`, this.generateSuggestion(type, context), finalSeverity)); } } } return issues; } analyzeContext(fileContent, line, column, language, framework, hasAuthorizationChecks, hasAuthentication, isProtectedRoute, issueType) { const lines = fileContent.lines; const currentLine = lines[line - 1] || ''; const surroundingLines = lines.slice(Math.max(0, line - 3), line + 2); return { isInComment: this.isInComment(currentLine, language, fileContent.content, line), isInString: this.isInString(currentLine, column), isInTestFile: this.isInTestFile(fileContent.path), isInDocumentation: this.isInDocumentation(fileContent.path), isInDevelopment: this.isInDevelopment(surroundingLines), surroundingCode: surroundingLines.join('\n'), language, framework, hasAuthorizationChecks: hasAuthorizationChecks || false, hasAuthentication: hasAuthentication || false, isProtectedRoute: isProtectedRoute || false, issueType }; } isSafeContext(context) { if (context.isInComment) return true; if (context.isInTestFile) return true; if (context.isInDocumentation) return true; if (context.isInDevelopment) return true; if (this.falsePositivePatterns.some(pattern => pattern.test(context.surroundingCode))) { return true; } return false; } isInComment(line, language, fullContent, lineNumber) { const trimmed = line.trim(); // Checks for single line comments if (language === 'javascript' || language === 'typescript') { if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) return true; } if (language === 'python') { if (trimmed.startsWith('#')) return true; } if (language === 'php') { if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('#')) return true; } // Checks for multi line comments const beforeContent = fullContent.split('\n').slice(0, lineNumber).join('\n'); for (const pattern of this.multiLineCommentPatterns) { const matches = beforeContent.match(pattern); if (matches && matches.length > 0) { // Checks if the current line is within a multi line comment const lastMatch = matches[matches.length - 1]; if (lastMatch) { const lastMatchIndex = beforeContent.lastIndexOf(lastMatch); const commentEndIndex = lastMatchIndex + lastMatch.length; // If we're still within the comment, returns true if (commentEndIndex >= beforeContent.length) { return true; } } } } return false; } isInTestFile(filePath) { return filePath.includes('test') || filePath.includes('spec') || filePath.includes('__tests__') || filePath.match(/\.(test|spec)\./i) !== null; } isInDocumentation(filePath) { const docPatterns = [ /docs?\//i, /documentation/i, /examples?/i, /samples?/i, /tutorials?/i, /guides?/i, /readme/i, /\.md$/i, /\.rst$/i, /\.txt$/i ]; return docPatterns.some(pattern => pattern.test(filePath)); } isInDevelopment(lines) { return lines.some(line => line.includes('development') || line.includes('dev') || line.includes('staging') || line.includes('localhost') || line.includes('127.0.0.1') || line.includes('NODE_ENV') || line.includes('DEBUG')); } hasAuthorizationChecks(content) { return this.authorizationPatterns.some(pattern => pattern.test(content)); } hasAuthentication(content) { const authPatterns = [ /auth\s*\(/i, /login\s*\(/i, /authenticate\s*\(/i, /session\s*\(/i, /token\s*\(/i, /jwt\s*\(/i ]; return authPatterns.some(pattern => pattern.test(content)); } isProtectedRoute(content) { const protectedPatterns = [ /admin/i, /user/i, /api/i, /dashboard/i, /settings/i, /profile/i, /account/i, /billing/i, /payment/i, /order/i ]; return protectedPatterns.some(pattern => pattern.test(content)); } calculateConfidence(baseConfidence, context) { let confidence = baseConfidence; // Adjusts confidence based on context if (context.hasAuthorizationChecks) confidence *= 0.6; // Reduces if auth checks present if (context.hasAuthentication) confidence *= 0.8; // Reduces if auth present if (context.framework) confidence *= 1.1; // Increases for known frameworks return Math.min(confidence, 1.0); } calculateSeverity(baseSeverity, context) { let severity = baseSeverity; // Never downgrades critical issues below medium if (baseSeverity === 'critical') { if (context.hasAuthorizationChecks) severity = 'high'; if (context.hasAuthentication) severity = 'high'; // Keep as critical if no auth measures present } // Adjust other severities if (context.hasAuthorizationChecks) { if (severity === 'high') severity = 'medium'; } return severity; } 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', 'cpp': 'cpp', 'c': 'c', 'rs': 'rust', 'kt': 'kotlin', 'swift': 'swift', 'dart': 'dart', 'scala': 'scala', 'clj': 'clojure', 'hs': 'haskell' }; 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 === 'php') { if (content.includes('laravel') || content.includes('Laravel')) return 'laravel'; if (content.includes('symfony') || content.includes('Symfony')) return 'symfony'; } return undefined; } isInString(line, column) { const before = line.substring(0, column); const quotes = (before.match(/['"`]/g) || []).length; return quotes % 2 === 1; } // Validation methods for different access control issues! validateProtectedRoute(text) { const protectedKeywords = ['admin', 'user', 'api', 'dashboard', 'settings', 'profile', 'account', 'billing', 'payment', 'order']; return protectedKeywords.some(keyword => text.toLowerCase().includes(keyword)); } validateDirectObjectReference(text) { return /findById\s*\(/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateDatabaseQuery(text) { return /findOne\s*\(/.test(text) && /id\s*:/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateMongoDBQuery(text) { return /find\s*\(/.test(text) && /_id\s*:/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateORMQuery(text) { return /where\s*\(/.test(text) && /id/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateFileAccess(text) { return /readFile\s*\(/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateFileWrite(text) { return /writeFile\s*\(/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateFileDeletion(text) { return /unlink\s*\(/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateDatabaseUpdate(text) { return /\.update\s*\(/.test(text) && /id\s*:/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateDatabaseDeletion(text) { return /\.delete\s*\(/.test(text) && /id\s*:/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateMongoDBRemoval(text) { return /\.remove\s*\(/.test(text) && /_id\s*:/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateRoleAssignment(text) { const roleKeywords = ['admin', 'user', 'role']; const inputKeywords = ['req.', 'request.', 'input.', 'params.', 'query.', 'body.']; return roleKeywords.some(role => text.toLowerCase().includes(role)) && inputKeywords.some(input => text.toLowerCase().includes(input)); } validatePermissionAssignment(text) { const permissionKeywords = ['permission', 'access']; const inputKeywords = ['req.', 'request.', 'input.', 'params.', 'query.', 'body.']; return permissionKeywords.some(permission => text.toLowerCase().includes(permission)) && inputKeywords.some(input => text.toLowerCase().includes(input)); } validateSessionManipulation(text) { return /req\.session\./.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validateSessionAssignment(text) { return /session\[/.test(text) && /req\.|request\.|input\.|params\.|query\.|body\./.test(text); } validatePHPSessionManipulation(text) { return /\$_SESSION\[/.test(text) && /\$_GET|\$_POST|\$_REQUEST/.test(text); } validatePHPDatabaseQuery(text) { return /SELECT\s+\*\s+FROM/.test(text) && /WHERE\s+id\s*=/.test(text) && /\$_GET|\$_POST|\$_REQUEST/.test(text); } validatePythonSessionManipulation(text) { return /session\[/.test(text) && /request\.|flask\.request\./.test(text); } validatePythonORMQuery(text) { return /User\.query\.filter_by\(id\s*=/.test(text) && /request\.|flask\.request\./.test(text); } validateJavaSessionManipulation(text) { return /session\.setAttribute/.test(text) && /request\.getParameter|request\.getAttribute/.test(text); } validateJavaRepositoryQuery(text) { return /userRepository\.findById/.test(text) && /request\.getParameter|request\.getAttribute/.test(text); } getLineContext(lineContent, column) { const start = Math.max(0, column - 20); const end = Math.min(lineContent.length, column + 20); return lineContent.substring(start, end).trim(); } generateSuggestion(type, context) { const suggestions = { 'Protected route without authorization': 'Implement authorization middleware for protected routes. Use role-based access control (RBAC) and route guards.', 'Direct object reference without ownership check': 'Verify user ownership before accessing resources. Use user context in queries and implement ownership validation.', 'Database query without ownership check': 'Add ownership validation to database queries. Filter by user ID or role and implement proper access controls.', 'MongoDB query without ownership check': 'Add ownership validation to MongoDB queries. Filter by user ID or role and implement proper access controls.', 'ORM query without ownership check': 'Add ownership validation to ORM queries. Use user context in filters and implement proper access controls.', 'File access without authorization': 'Implement file access controls and validate user permissions before file operations. Use secure file handling.', 'File write without authorization': 'Implement file write controls and validate user permissions before file operations. Use secure file handling.', 'File deletion without authorization': 'Implement file deletion controls and validate user permissions before file operations. Use secure file handling.', 'Database update without user context': 'Add user context to database updates. Ensure users can only update their own data and implement proper validation.', 'Database deletion without user context': 'Add user context to database deletions. Ensure users can only delete their own data and implement proper validation.', 'MongoDB removal without user context': 'Add user context to MongoDB removals. Ensure users can only remove their own data and implement proper validation.', 'Role assignment from user input': 'Never assign roles directly from user input. Use server-side role validation and implement proper role management.', 'Permission assignment from user input': 'Never assign permissions directly from user input. Use server-side permission validation and implement proper permission management.', 'Session manipulation with user input': 'Never manipulate session data with user input. Use server-side session management and implement proper session controls.', 'Session assignment with user input': 'Never assign session data with user input. Use server-side session management and implement proper session controls.', 'PHP session manipulation with user input': 'Never manipulate PHP session data with user input. Use server-side session management and implement proper session controls.', 'PHP database query without authorization': 'Add authorization checks to PHP database queries. Use prepared statements and implement proper access controls.', 'Python session manipulation with user input': 'Never manipulate Python session data with user input. Use server-side session management and implement proper session controls.', 'Python ORM query without authorization': 'Add authorization checks to Python ORM queries. Filter by user context and implement proper access controls.', 'Java session manipulation with user input': 'Never manipulate Java session data with user input. Use server-side session management and implement proper session controls.', 'Java repository query without authorization': 'Add authorization checks to Java repository queries. Filter by user context and implement proper access controls.' }; let suggestion = suggestions[type] || 'Implement proper authorization checks. Verify user ownership, check roles/permissions, and ensure users can only access their own resources.'; if (context.framework) { suggestion += ` For ${context.framework}, consider using framework-specific authorization patterns.`; if (context.framework === 'express') { suggestion += ' Use express-session, passport.js, and authorization middleware.'; } else if (context.framework === 'django') { suggestion += ' Use Django\'s built-in authentication and permission system.'; } else if (context.framework === 'laravel') { suggestion += ' Use Laravel\'s Gates and Policies for authorization.'; } else if (context.framework === 'react') { suggestion += ' Implement client-side route guards and server-side authorization.'; } } // Context aware suggestions based on issue type! if (context.issueType?.includes('route')) { suggestion += ' Consider implementing route-level authorization middleware.'; } else if (context.issueType?.includes('database') || context.issueType?.includes('query')) { suggestion += ' Use parameterized queries and implement data-level authorization.'; } else if (context.issueType?.includes('session')) { suggestion += ' Implement secure session management and validation.'; } else if (context.issueType?.includes('file')) { suggestion += ' Use secure file handling libraries and implement file-level permissions.'; } return suggestion; } } exports.BrokenAccessControlRule = BrokenAccessControlRule; //# sourceMappingURL=broken-access-control.js.map