UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

326 lines (325 loc) 12.8 kB
"use strict"; /** * Detector for hardcoded secrets, API keys, passwords, and tokens */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HardcodedSecretDetector = void 0; const typescript_1 = __importDefault(require("typescript")); const BaseDetector_1 = require("./BaseDetector"); const PatternMatcher_1 = require("../utils/PatternMatcher"); const ASTTraverser_1 = require("../utils/ASTTraverser"); const debugging_constants_1 = require("../constants/debugging.constants"); const sensitiveData_constants_1 = require("../constants/sensitiveData.constants"); class HardcodedSecretDetector extends BaseDetector_1.BaseDetector { constructor() { super("HardcodedSecretDetector", "hardcoded-secret", "critical", HardcodedSecretDetector.SECRET_PATTERNS); } async detect(scanResult) { const vulnerabilities = []; // Filter relevant files (exclude node_modules, dist, etc.) // TODO: MOVE TO CONSTANTS const relevantFiles = this.filterRelevantFiles(scanResult, [".ts", ".tsx", ".js", ".jsx", ".json"], ["node_modules", "dist", "build", ".git", "coverage"]); 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.validateSecretMatch(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.analyzeASTForSecrets(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 is actually a secret */ validateSecretMatch(matchResult) { const match = matchResult.matches[0]; if (!match) return false; const matchedText = match.match; // Check if it's in a comment if (this.isInComment(match.context || "", match.match)) { return false; } // Check if it's a placeholder or example if (this.isPlaceholderValue(matchedText)) { return false; } // Check entropy for potential secrets const pattern = matchResult.pattern; if (pattern.entropyThreshold) { const entropy = PatternMatcher_1.PatternMatcher.calculateEntropy(matchedText); if (entropy < pattern.entropyThreshold) { return false; } } // Length validation if (pattern.minLength && matchedText.length < pattern.minLength) { return false; } if (pattern.maxLength && matchedText.length > pattern.maxLength) { return false; } return true; } /** * AST-based secret detection for more sophisticated analysis */ analyzeASTForSecrets(sourceFile, filePath) { const vulnerabilities = []; // Find all string literals const stringLiterals = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.StringLiteral); for (const stringLiteral of stringLiterals) { const text = stringLiteral.text; // Skip short strings if (text.length < 16) continue; // Check if it looks like a secret if (this.looksLikeSecret(text, stringLiteral)) { const location = ASTTraverser_1.ASTTraverser.getNodeLocation(stringLiteral, sourceFile); const context = ASTTraverser_1.ASTTraverser.getNodeContext(stringLiteral, sourceFile); const vulnerability = this.createVulnerability(filePath, { line: location.line, column: location.column, endLine: location.line, endColumn: location.column + text.length, }, { code: text, surroundingContext: context, functionName: this.extractFunctionFromAST(stringLiteral), componentName: this.extractComponentName(filePath), }, "Potential hardcoded secret detected in string literal", "critical", "medium", { entropy: PatternMatcher_1.PatternMatcher.calculateEntropy(text), length: text.length, detectionMethod: "ast-analysis", }); vulnerabilities.push(vulnerability); } } return vulnerabilities; } /** * Check if a string looks like a secret based on various heuristics */ looksLikeSecret(text, node) { // Calculate entropy const entropy = PatternMatcher_1.PatternMatcher.calculateEntropy(text); if (entropy < 4.0) return false; // Check if it has mixed character types if (!PatternMatcher_1.PatternMatcher.isPotentialSecret(text, 4.0, 16)) { return false; } // Check the variable name or property name const variableName = this.getVariableNameForStringLiteral(node); if (variableName && this.isSensitiveVariableName(variableName)) { return true; } // Check if it's assigned to a sensitive property const parent = node.parent; if (typescript_1.default.isPropertyAssignment(parent) && typescript_1.default.isIdentifier(parent.name)) { const propertyName = parent.name.text; if (this.isSensitiveVariableName(propertyName)) { return true; } } return false; } /** * Check if a variable name suggests it contains sensitive data */ isSensitiveVariableName(name) { const lowerName = name.toLowerCase(); return sensitiveData_constants_1.SENSITIVE_VARIABLE_NAMES.some((sensitive) => lowerName.includes(sensitive) || lowerName.replace(/[_-]/g, "").includes(sensitive.replace(/[_-]/g, ""))); } /** * Get variable name for a string literal */ getVariableNameForStringLiteral(node) { let parent = node.parent; // Check if it's a variable declaration if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } // Check if it's a property assignment if (typescript_1.default.isPropertyAssignment(parent) && typescript_1.default.isIdentifier(parent.name)) { return parent.name.text; } return undefined; } /** * Fixed extractFunctionFromAST function */ 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; } /** * Check if value is a placeholder or example */ isPlaceholderValue(value) { return debugging_constants_1.PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value)); } } exports.HardcodedSecretDetector = HardcodedSecretDetector; HardcodedSecretDetector.SECRET_PATTERNS = [ { id: "aws-access-key", name: "AWS Access Key", description: "Hardcoded AWS access key detected", pattern: { type: "regex", expression: /AKIA[0-9A-Z]{16}/g, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, entropyThreshold: 4.5, minLength: 20, knownPrefixes: ["AKIA"], }, { id: "aws-secret-key", name: "AWS Secret Key", description: "Hardcoded AWS secret access key detected", pattern: { type: "regex", expression: /[A-Za-z0-9\/\+=]{40}/g, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, entropyThreshold: 5.0, minLength: 40, maxLength: 40, }, { id: "github-token", name: "GitHub Token", description: "Hardcoded GitHub personal access token detected", pattern: { type: "regex", expression: /ghp_[a-zA-Z0-9]{36}/g, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, knownPrefixes: ["ghp_"], }, { id: "api-key-generic", name: "Generic API Key", description: "Hardcoded API key detected", pattern: { type: "regex", expression: /(?:api[_-]?key|apikey|key)\s*[:=]\s*['"]([a-zA-Z0-9\-_]{20,})['"]/gi, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, entropyThreshold: 4.0, minLength: 20, }, { id: "database-url", name: "Database Connection String", description: "Hardcoded database connection string detected", pattern: { type: "regex", expression: /(?:mongodb|mysql|postgres|postgresql):\/\/[^\s'"]+/gi, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, }, { id: "jwt-secret", name: "JWT Secret", description: "Hardcoded JWT secret detected", pattern: { type: "regex", expression: /(?:jwt[_-]?secret|jwtsecret)\s*[:=]\s*['"]([a-zA-Z0-9\-_+=\/]{16,})['"]/gi, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, entropyThreshold: 4.5, minLength: 16, }, { id: "private-key", name: "Private Key", description: "Hardcoded private key detected", pattern: { type: "regex", expression: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/gi, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "high", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json", ".pem", ".key"], enabled: true, }, { id: "password-hardcoded", name: "Hardcoded Password", description: "Hardcoded password detected", pattern: { type: "regex", expression: /(?:password|passwd|pwd)\s*[:=]\s*['"](?!.*\$\{)[a-zA-Z0-9\-_!@#$%^&*()+=]{8,}['"]/gi, }, vulnerabilityType: "hardcoded-secret", severity: "critical", confidence: "medium", fileTypes: [".ts", ".tsx", ".js", ".jsx", ".json"], enabled: true, entropyThreshold: 3.5, minLength: 8, }, ];