UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

317 lines (316 loc) 13.4 kB
"use strict"; /** * Detector for console logging of sensitive data */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConsoleLoggingDetector = void 0; const typescript_1 = __importDefault(require("typescript")); const BaseDetector_1 = require("./BaseDetector"); const ASTTraverser_1 = require("../utils/ASTTraverser"); const debugging_constants_1 = require("../constants/debugging.constants"); const sensitiveData_constants_1 = require("../constants/sensitiveData.constants"); class ConsoleLoggingDetector extends BaseDetector_1.BaseDetector { constructor() { super("ConsoleLoggingDetector", "console-logging-sensitive", "critical", ConsoleLoggingDetector.CONSOLE_PATTERNS); } async detect(scanResult) { const vulnerabilities = []; // Filter relevant files (exclude test files as they might legitimately log test data) // 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; // Apply pattern matching const patternResults = this.applyPatternMatching(content, filePath); const patternVulnerabilities = this.convertPatternMatchesToVulnerabilities(patternResults, (match) => this.validateConsoleMatch(match, content)); // Apply AST-based analysis for more sophisticated detection const sourceFile = scanResult.sourceFiles.get(filePath); if (sourceFile) { const astVulnerabilities = this.applyASTAnalysis(sourceFile, filePath, (sf, fp) => this.analyzeASTForConsoleLogging(sf, fp)); vulnerabilities.push(...astVulnerabilities); } // Adjust confidence based on file context const fileContext = this.getFileContext(filePath, content); for (const vuln of patternVulnerabilities) { vuln.confidence = this.adjustConfidenceBasedOnContext(vuln, fileContext); if (this.validateVulnerability(vuln)) { vulnerabilities.push(vuln); } } } return vulnerabilities; } /** * Validate if a pattern match represents actual sensitive console logging */ validateConsoleMatch(matchResult, content) { 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 test code (test files might legitimately log test data) if (this.isInTestContext(match.context || "")) { return false; } return true; } /** * AST-based analysis for console logging detection */ analyzeASTForConsoleLogging(sourceFile, filePath) { const vulnerabilities = []; // Find all console method calls const consoleCallExpressions = this.findConsoleCallExpressions(sourceFile); for (const callExpr of consoleCallExpressions) { const sensitivity = this.analyzeConsoleSensitivity(callExpr, sourceFile); if (sensitivity) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(callExpr, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(callExpr, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(callExpr, 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(callExpr), }, sensitivity.description, "critical", sensitivity.confidence, { consoleMethod: sensitivity.method, sensitiveVariables: sensitivity.sensitiveVariables, detectionMethod: "ast-analysis", }); vulnerabilities.push(vulnerability); } } return vulnerabilities; } /** * Find all console method call expressions */ findConsoleCallExpressions(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 === "console" && typescript_1.default.isIdentifier(method) && this.isConsoleMethod(method.text)); } return false; }); } /** * Check if method name is a console logging method */ isConsoleMethod(methodName) { return debugging_constants_1.CONSOLE_METHODS.includes(methodName); } /** * Analyze console call for sensitivity */ analyzeConsoleSensitivity(callExpr, sourceFile) { const methodName = this.getConsoleMethodName(callExpr); if (!methodName) return null; const sensitiveVariables = []; const argumentTexts = []; // Analyze each argument to the console call for (const arg of callExpr.arguments) { const argText = ASTTraverser_1.ASTTraverser.getNodeText(arg, sourceFile); argumentTexts.push(argText); // Check for sensitive variable names const sensitiveVars = this.extractSensitiveVariables(arg, sourceFile); sensitiveVariables.push(...sensitiveVars); } if (sensitiveVariables.length === 0) { return null; } // Determine confidence based on how explicit the sensitive data logging is let confidence = "medium"; // High confidence if variable names are explicitly sensitive if (sensitiveVariables.some((v) => this.isExplicitlySensitive(v))) { confidence = "high"; } // Medium confidence if only potentially sensitive if (sensitiveVariables.every((v) => this.isPotentiallySensitive(v))) { confidence = "medium"; } return { description: `Console.${methodName}() logging potentially sensitive data: ${sensitiveVariables.join(", ")}`, confidence, method: methodName, sensitiveVariables, }; } /** * Extract console method name from call expression */ getConsoleMethodName(callExpr) { if (typescript_1.default.isPropertyAccessExpression(callExpr.expression) && typescript_1.default.isIdentifier(callExpr.expression.name)) { return callExpr.expression.name.text; } return null; } /** * Extract sensitive variable names from an argument expression */ extractSensitiveVariables(expr, sourceFile) { const sensitiveVars = []; // Handle different expression types if (typescript_1.default.isIdentifier(expr)) { if (this.isSensitiveVariableName(expr.text)) { sensitiveVars.push(expr.text); } } else if (typescript_1.default.isPropertyAccessExpression(expr)) { const propertyName = typescript_1.default.isIdentifier(expr.name) ? expr.name.text : ""; if (this.isSensitiveVariableName(propertyName)) { sensitiveVars.push(propertyName); } } else if (typescript_1.default.isObjectLiteralExpression(expr)) { // Check object properties for (const prop of expr.properties) { if (typescript_1.default.isPropertyAssignment(prop) && typescript_1.default.isIdentifier(prop.name)) { if (this.isSensitiveVariableName(prop.name.text)) { sensitiveVars.push(prop.name.text); } } } } else if (typescript_1.default.isTemplateExpression(expr)) { // Check template literal expressions for variable references for (const span of expr.templateSpans) { const spanVars = this.extractSensitiveVariables(span.expression, sourceFile); sensitiveVars.push(...spanVars); } } return sensitiveVars; } /** * Check if variable name suggests sensitive data */ isSensitiveVariableName(name) { const lowerName = name.toLowerCase(); return sensitiveData_constants_1.CONSOLE_SENSITIVE_KEYWORDS.some((keyword) => lowerName.includes(keyword) || lowerName.replace(/[_-]/g, "").includes(keyword.replace(/[_-]/g, ""))); } /** * Check if variable name is explicitly sensitive (high confidence) */ isExplicitlySensitive(name) { const lowerName = name.toLowerCase(); return sensitiveData_constants_1.EXPLICIT_SENSITIVE_KEYWORDS.some((keyword) => lowerName.includes(keyword)); } /** * Check if variable name is potentially sensitive (medium confidence) */ isPotentiallySensitive(name) { const lowerName = name.toLowerCase(); return sensitiveData_constants_1.POTENTIAL_SENSITIVE_KEYWORDS.some((keyword) => lowerName.includes(keyword)); } /** * 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.ConsoleLoggingDetector = ConsoleLoggingDetector; ConsoleLoggingDetector.CONSOLE_PATTERNS = [ { id: "console-log-password", name: "Console logging password", description: "Console logging of password detected - sensitive data should not be logged", pattern: { type: "regex", expression: /console\.(log|warn|error|info|debug)\s*\([^)]*password[^)]*\)/gi, }, vulnerabilityType: "console-logging-sensitive", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "console-log-token", name: "Console logging token", description: "Console logging of token detected - sensitive data should not be logged", pattern: { type: "regex", expression: /console\.(log|warn|error|info|debug)\s*\([^)]*token[^)]*\)/gi, }, vulnerabilityType: "console-logging-sensitive", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "console-log-secret", name: "Console logging secret", description: "Console logging of secret detected - sensitive data should not be logged", pattern: { type: "regex", expression: /console\.(log|warn|error|info|debug)\s*\([^)]*secret[^)]*\)/gi, }, vulnerabilityType: "console-logging-sensitive", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "console-log-key", name: "Console logging key", description: "Console logging of key detected - sensitive data should not be logged", pattern: { type: "regex", expression: /console\.(log|warn|error|info|debug)\s*\([^)]*\b(api_?key|private_?key|encryption_?key)\b[^)]*\)/gi, }, vulnerabilityType: "console-logging-sensitive", severity: "critical", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, ];