UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

350 lines (349 loc) 15.8 kB
"use strict"; /** * Detector for insecure random number generation in security contexts */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InsecureRandomDetector = void 0; const typescript_1 = __importDefault(require("typescript")); const BaseDetector_1 = require("./BaseDetector"); const ASTTraverser_1 = require("../utils/ASTTraverser"); const security_constants_1 = require("../constants/security.constants"); class InsecureRandomDetector extends BaseDetector_1.BaseDetector { constructor() { super("InsecureRandomDetector", "insecure-random", "high", InsecureRandomDetector.RANDOM_PATTERNS); } async detect(scanResult) { const vulnerabilities = []; // Filter relevant files // TODO: MOVE TO CONSTANTS const relevantFiles = this.filterRelevantFiles(scanResult, [".ts", ".tsx", ".js", ".jsx"], [ "node_modules", "dist", "build", ".git", "coverage", "__tests__", ".test.", ".spec.", ]); for (const filePath of relevantFiles) { const content = scanResult.fileContents.get(filePath); if (!content) continue; // Check if file has secure random alternatives const hasSecureAlternatives = this.detectSecureRandomUsage(content); // Apply pattern matching const patternResults = this.applyPatternMatching(content, filePath); const patternVulnerabilities = this.convertPatternMatchesToVulnerabilities(patternResults, (match) => this.validateRandomMatch(match)); // Apply AST-based analysis for context-aware detection const sourceFile = scanResult.sourceFiles.get(filePath); if (sourceFile) { const astVulnerabilities = this.applyASTAnalysis(sourceFile, filePath, (sf, fp) => this.analyzeASTForInsecureRandom(sf, fp, hasSecureAlternatives)); vulnerabilities.push(...astVulnerabilities); } // Adjust confidence based on file context and security usage const fileContext = this.getFileContext(filePath, content); for (const vuln of patternVulnerabilities) { // Increase confidence if file handles sensitive data if (fileContext.handlesSensitiveData || fileContext.riskContexts.includes("authentication")) { vuln.confidence = "high"; } // Add metadata about secure alternatives if not already used if (hasSecureAlternatives.length === 0) { vuln.metadata = { ...vuln.metadata, secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES, note: "Consider using cryptographically secure random generation", }; } vuln.confidence = this.adjustConfidenceBasedOnContext(vuln, fileContext); if (this.validateVulnerability(vuln)) { vulnerabilities.push(vuln); } } } return vulnerabilities; } /** * Detect usage of secure random alternatives in the file */ detectSecureRandomUsage(content) { const foundAlternatives = []; for (const alternative of security_constants_1.SECURE_RANDOM_ALTERNATIVES) { if (content.includes(alternative)) { foundAlternatives.push(alternative); } } return foundAlternatives; } /** * Validate if a random pattern match is in a security context */ validateRandomMatch(matchResult) { const match = matchResult.matches[0]; if (!match) return false; // Check if it's in a comment if (this.isInComment(match.context || "", match.match)) { return false; } // Check if it's in a security context return this.isInSecurityContext(match.context || ""); } /** * AST-based analysis for insecure random usage */ analyzeASTForInsecureRandom(sourceFile, filePath, secureAlternatives) { const vulnerabilities = []; // Find Math.random() calls const mathRandomCalls = this.findMathRandomCalls(sourceFile); for (const randomCall of mathRandomCalls) { const securityContext = this.analyzeRandomSecurityContext(randomCall, sourceFile); if (securityContext) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(randomCall, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(randomCall, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(randomCall, sourceFile); const vulnerability = this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(randomCall), }, securityContext.description, "high", securityContext.confidence, { securityContext: securityContext.contextType, variableName: securityContext.variableName, hasSecureAlternatives: secureAlternatives.length > 0, secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES, detectionMethod: "ast-analysis", }); vulnerabilities.push(vulnerability); } } // Find Date.now() usage for randomness const dateNowCalls = this.findDateNowRandomUsage(sourceFile); for (const dateCall of dateNowCalls) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(dateCall, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(dateCall, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(dateCall, sourceFile); const vulnerability = this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(dateCall), }, "Date.now() used for randomness - this is predictable and insecure for cryptographic purposes", "high", "medium", { randomnessMethod: "date-based", secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES, detectionMethod: "ast-analysis", }); vulnerabilities.push(vulnerability); } return vulnerabilities; } /** * Find Math.random() call expressions */ findMathRandomCalls(sourceFile) { const callExpressions = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.CallExpression); return callExpressions.filter((callExpr) => { if (typescript_1.default.isPropertyAccessExpression(callExpr.expression)) { const obj = callExpr.expression.expression; const method = callExpr.expression.name; return (typescript_1.default.isIdentifier(obj) && obj.text === "Math" && typescript_1.default.isIdentifier(method) && method.text === "random"); } return false; }); } /** * Find Date.now() usage in random contexts */ findDateNowRandomUsage(sourceFile) { const randomUsages = []; const callExpressions = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.CallExpression); for (const callExpr of callExpressions) { if (typescript_1.default.isPropertyAccessExpression(callExpr.expression)) { const obj = callExpr.expression.expression; const method = callExpr.expression.name; if (typescript_1.default.isIdentifier(obj) && obj.text === "Date" && typescript_1.default.isIdentifier(method) && method.text === "now") { // Check if it's used in a modulo operation (common for "randomness") const parent = callExpr.parent; if (typescript_1.default.isBinaryExpression(parent) && parent.operatorToken.kind === typescript_1.default.SyntaxKind.PercentToken) { randomUsages.push(parent); } } } } return randomUsages; } /** * Analyze the security context of a Math.random() call */ analyzeRandomSecurityContext(randomCall, sourceFile) { // Get the variable name this random call is assigned to const variableName = this.getAssignmentVariableName(randomCall); // Check if variable name suggests security context if (variableName && this.isSecurityRelatedVariable(variableName)) { return { description: `Math.random() used for security-sensitive variable '${variableName}' - use cryptographically secure random instead`, confidence: "high", contextType: "variable-assignment", variableName, }; } // Check function context const functionName = this.extractFunctionFromAST(randomCall); if (functionName && this.isSecurityRelatedFunction(functionName)) { return { description: `Math.random() used in security-related function '${functionName}' - use cryptographically secure random instead`, confidence: "high", contextType: "function-context", variableName: functionName, }; } // Check surrounding code context const context = ASTTraverser_1.ASTTraverser.getNodeContext(randomCall, sourceFile, 5); if (this.isInSecurityContext(context)) { return { description: "Math.random() used in security context - use cryptographically secure random instead", confidence: "medium", contextType: "contextual", variableName, }; } return null; } /** * Get the variable name that a random call is assigned to */ getAssignmentVariableName(node) { let parent = node.parent; // Walk up to find variable declaration or assignment while (parent) { if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } if (typescript_1.default.isBinaryExpression(parent) && parent.operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken && typescript_1.default.isIdentifier(parent.left)) { return parent.left.text; } if (typescript_1.default.isPropertyAssignment(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } parent = parent.parent; } return undefined; } /** * Check if variable name suggests security context */ isSecurityRelatedVariable(name) { const lowerName = name.toLowerCase(); // First check if it's a UI/visual context (should NOT be flagged) if (security_constants_1.UI_VISUAL_CONTEXTS.some((context) => lowerName.includes(context))) { return false; } // Then check if it's actually security-related return security_constants_1.SECURITY_CONTEXTS.some((context) => lowerName.includes(context) || lowerName.replace(/[_-]/g, "").includes(context.replace(/[_-]/g, ""))); } /** * Check if function name suggests security context */ isSecurityRelatedFunction(name) { const lowerName = name.toLowerCase(); // First check if it's a UI/visual function (should NOT be flagged) if (security_constants_1.UI_FUNCTION_PATTERNS.some((pattern) => pattern.test(lowerName))) { return false; } // Then check if it's actually security-related return security_constants_1.SECURITY_FUNCTIONS.some((func) => lowerName.includes(func) && security_constants_1.SECURITY_CONTEXTS.some((ctx) => lowerName.includes(ctx))); } /** * Check if context suggests security usage */ isInSecurityContext(context) { const lowerContext = context.toLowerCase(); // Check if it's clearly a UI/visual context (should NOT be flagged) if (security_constants_1.UI_VISUAL_CONTEXTS.some((uiContext) => lowerContext.includes(uiContext))) { return false; } if (security_constants_1.UI_FUNCTION_PATTERNS.some((pattern) => pattern.test(lowerContext))) { return false; } // Then check if it's actually security-related return security_constants_1.SECURITY_CONTEXTS.some((secContext) => lowerContext.includes(secContext)); } /** * Extract function name from AST node context */ extractFunctionFromAST(node) { let current = node.parent; while (current) { if (typescript_1.default.isFunctionDeclaration(current) && current.name) { return current.name.text; } if (typescript_1.default.isMethodDeclaration(current) && typescript_1.default.isIdentifier(current.name)) { return current.name.text; } if (typescript_1.default.isVariableDeclaration(current) && typescript_1.default.isIdentifier(current.name) && current.initializer && (typescript_1.default.isFunctionExpression(current.initializer) || typescript_1.default.isArrowFunction(current.initializer))) { return current.name.text; } current = current.parent; } return undefined; } } exports.InsecureRandomDetector = InsecureRandomDetector; InsecureRandomDetector.RANDOM_PATTERNS = [ { id: "math-random-security", name: "Math.random() in security context", description: "Math.random() used in security context - use cryptographically secure random instead", pattern: { type: "regex", expression: /Math\.random\s*\(\s*\)/g, }, vulnerabilityType: "insecure-random", severity: "high", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "date-now-random", name: "Date.now() used for randomness", description: "Date.now() used for randomness - this is predictable and insecure", pattern: { type: "regex", expression: /Date\.now\s*\(\s*\)\s*%/g, }, vulnerabilityType: "insecure-random", severity: "high", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, ];