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