sicua
Version:
A tool for analyzing project structure and dependencies
407 lines (406 loc) • 17.3 kB
JavaScript
"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,
},
];