UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

407 lines (406 loc) 17.3 kB
"use strict"; /** * Detector for debug code and development flags in production code */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DebugCodeDetector = 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 environment_constants_1 = require("../constants/environment.constants"); const security_constants_1 = require("../constants/security.constants"); class DebugCodeDetector extends BaseDetector_1.BaseDetector { constructor() { super("DebugCodeDetector", "debug-code", "high", DebugCodeDetector.DEBUG_PATTERNS); } async detect(scanResult) { const vulnerabilities = []; // Filter relevant files (exclude test files as they may legitimately contain debug code) // 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.validateDebugMatch(match)); // 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.analyzeASTForDebugCode(sf, fp)); vulnerabilities.push(...astVulnerabilities); } // Adjust confidence based on file context const fileContext = this.getFileContext(filePath, content); for (const vuln of patternVulnerabilities) { // Lower confidence for development/config files if (fileContext.fileType === "config" || filePath.includes("dev") || filePath.includes("development")) { vuln.confidence = "low"; } vuln.confidence = this.adjustConfidenceBasedOnContext(vuln, fileContext); if (this.validateVulnerability(vuln)) { vulnerabilities.push(vuln); } } } return vulnerabilities; } /** * Validate if a debug code match is problematic */ validateDebugMatch(matchResult) { const match = matchResult.matches[0]; if (!match) return false; // Check if it's in a comment (comments with TODO/FIXME are still flagged) if (this.isInComment(match.context || "", match.match)) { // Allow TODO/FIXME in comments but flag debugger statements return match.match.includes("debugger"); } // Check if it's properly gated by environment checks if (this.isProperlyGated(match.context || "")) { return false; } return true; } /** * AST-based analysis for debug code detection */ analyzeASTForDebugCode(sourceFile, filePath) { const vulnerabilities = []; // Find debugger statements const debuggerStatements = this.findDebuggerStatements(sourceFile); for (const debugStmt of debuggerStatements) { const debugVuln = this.analyzeDebuggerStatement(debugStmt, sourceFile, filePath); if (debugVuln) { vulnerabilities.push(debugVuln); } } // Find debug-related variables and flags const debugVariables = this.findDebugVariables(sourceFile); for (const debugVar of debugVariables) { const debugVuln = this.analyzeDebugVariable(debugVar, sourceFile, filePath); if (debugVuln) { vulnerabilities.push(debugVuln); } } // Find console debug methods const consoleDebugCalls = this.findConsoleDebugCalls(sourceFile); for (const consoleCall of consoleDebugCalls) { const consoleVuln = this.analyzeConsoleDebugCall(consoleCall, sourceFile, filePath); if (consoleVuln) { vulnerabilities.push(consoleVuln); } } // Find development-only code blocks const devCodeBlocks = this.findDevelopmentCodeBlocks(sourceFile); for (const devBlock of devCodeBlocks) { const devVuln = this.analyzeDevelopmentCodeBlock(devBlock, sourceFile, filePath); if (devVuln) { vulnerabilities.push(devVuln); } } return vulnerabilities; } /** * Find debugger statements */ findDebuggerStatements(sourceFile) { return ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.DebuggerStatement); } /** * Find debug-related variables */ findDebugVariables(sourceFile) { return ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.VariableDeclaration, (node) => { if (typescript_1.default.isIdentifier(node.name)) { return this.isDebugRelatedVariable(node.name.text); } return false; }); } /** * Find console debug method calls */ findConsoleDebugCalls(sourceFile) { return ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.CallExpression, (node) => { if (typescript_1.default.isPropertyAccessExpression(node.expression)) { const obj = node.expression.expression; const method = node.expression.name; return (typescript_1.default.isIdentifier(obj) && obj.text === "console" && typescript_1.default.isIdentifier(method) && debugging_constants_1.DEBUG_CONSOLE_METHODS.includes(method.text)); } return false; }); } /** * Find development-only code blocks */ findDevelopmentCodeBlocks(sourceFile) { return ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.IfStatement, (node) => this.isDevelopmentOnlyCondition(node.expression)); } /** * Analyze debugger statement */ analyzeDebuggerStatement(debugStmt, sourceFile, filePath) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(debugStmt, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(debugStmt, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(debugStmt, sourceFile); return this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(debugStmt), }, "debugger statement found - this should be removed before production deployment", "high", "high", { debugType: "debugger-statement", suggestion: "Remove debugger statement before production deployment", detectionMethod: "ast-analysis", }); } /** * Analyze debug variable */ analyzeDebugVariable(debugVar, sourceFile, filePath) { if (!typescript_1.default.isIdentifier(debugVar.name)) return null; const varName = debugVar.name.text; const location = ASTTraverser_1.ASTTraverser.getNodeLocation(debugVar, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(debugVar, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(debugVar, sourceFile); // Check if it's set to true or a truthy value const isSetToTrue = this.isVariableSetToTrue(debugVar); const isProperlyGated = this.isProperlyGated(context); if (isSetToTrue && !isProperlyGated) { return this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(debugVar), }, `Debug variable '${varName}' is permanently set to true - this may expose debug information in production`, "high", isProperlyGated ? "low" : "medium", { debugType: "debug-variable", variableName: varName, isGated: isProperlyGated, suggestion: "Ensure debug variables are properly gated by environment checks", detectionMethod: "ast-analysis", }); } return null; } /** * Analyze console debug call */ analyzeConsoleDebugCall(consoleCall, sourceFile, filePath) { const methodName = this.getConsoleMethodName(consoleCall); if (!methodName) return null; const location = ASTTraverser_1.ASTTraverser.getNodeLocation(consoleCall, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(consoleCall, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(consoleCall, sourceFile); const isProperlyGated = this.isProperlyGated(context); // If properly gated with development checks, lower the severity const severity = isProperlyGated ? "medium" : "high"; const confidence = isProperlyGated ? "low" : "medium"; return this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(consoleCall), }, `console.${methodName}() call detected - debug console methods should be removed or gated in production`, severity, confidence, { debugType: "console-debug", methodName, isGated: isProperlyGated, suggestion: "Remove debug console methods or gate them with environment checks", detectionMethod: "ast-analysis", }); } /** * Analyze development code block */ analyzeDevelopmentCodeBlock(ifStmt, sourceFile, filePath) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(ifStmt, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(ifStmt, sourceFile); const code = ASTTraverser_1.ASTTraverser.getNodeText(ifStmt.expression, sourceFile); // Check if this is a proper development gating pattern const isProperGating = security_constants_1.DEVELOPMENT_GATING_PATTERNS.some((pattern) => pattern.test(code)); // Don't flag proper development gating patterns if (isProperGating) { return null; } return this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + code.length, }, { code, surroundingContext: context, functionName: this.extractFunctionFromAST(ifStmt), }, "Development-only code block detected - verify this is properly implemented for production", "high", "low", { debugType: "development-block", condition: code, suggestion: "Verify development checks are correct and code is properly gated", detectionMethod: "ast-analysis", }); } /** * Check if variable name is debug-related */ isDebugRelatedVariable(name) { const lowerName = name.toLowerCase(); return environment_constants_1.DEVELOPMENT_FLAGS.some((flag) => lowerName.includes(flag.toLowerCase()) || lowerName .replace(/[_-]/g, "") .includes(flag.toLowerCase().replace(/[_-]/g, ""))); } /** * Check if variable is set to true */ isVariableSetToTrue(varDecl) { if (varDecl.initializer) { if (varDecl.initializer.kind === typescript_1.default.SyntaxKind.TrueKeyword) { return true; } if (typescript_1.default.isStringLiteral(varDecl.initializer) && (varDecl.initializer.text === "true" || varDecl.initializer.text === "1")) { return true; } } return false; } /** * Check if condition is development-only */ isDevelopmentOnlyCondition(expr) { const exprText = expr.getText(); return environment_constants_1.DEVELOPMENT_FLAGS.some((flag) => exprText.includes(flag)); } /** * Get console method name */ getConsoleMethodName(callExpr) { if (typescript_1.default.isPropertyAccessExpression(callExpr.expression) && typescript_1.default.isIdentifier(callExpr.expression.name)) { return callExpr.expression.name.text; } return null; } /** * Check if debug code is properly gated by environment checks */ isProperlyGated(context) { return security_constants_1.DEVELOPMENT_GATING_PATTERNS.some((pattern) => pattern.test(context)); } /** * 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.DebugCodeDetector = DebugCodeDetector; DebugCodeDetector.DEBUG_PATTERNS = [ { id: "debugger-statement", name: "debugger statement", description: "debugger statement detected - this should be removed before production", pattern: { type: "regex", expression: /\bdebugger\s*;?/g, }, vulnerabilityType: "debug-code", severity: "high", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "debug-flag-true", name: "Debug flag set to true", description: "Debug flag permanently set to true - this may expose debug information in production", pattern: { type: "regex", expression: /(?:debug|DEBUG|isDebug|debugMode|isDev|devMode)\s*[:=]\s*true/g, }, vulnerabilityType: "debug-code", severity: "high", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "console-debug-methods", name: "Console debug methods", description: "Debug console methods detected - these should be removed or properly gated in production", pattern: { type: "regex", expression: /console\.(debug|trace|group|groupCollapsed|groupEnd|time|timeEnd|profile|profileEnd)/g, }, vulnerabilityType: "debug-code", severity: "high", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, { id: "development-only-code", name: "Development-only code", description: "Development-only code block detected - verify this is properly gated for production", pattern: { type: "regex", expression: /if\s*\(\s*(?:process\.env\.NODE_ENV\s*===?\s*['"]development['"]|isDev|isDebug|debugMode)\s*\)/g, }, vulnerabilityType: "debug-code", severity: "high", confidence: "low", fileTypes: [".ts", ".tsx", ".js", ".jsx"], enabled: true, }, ];