sicua
Version:
A tool for analyzing project structure and dependencies
350 lines (349 loc) • 15.8 kB
JavaScript
"use strict";
/**
* Detector for insecure random number generation in security contexts
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InsecureRandomDetector = void 0;
const typescript_1 = __importDefault(require("typescript"));
const BaseDetector_1 = require("./BaseDetector");
const ASTTraverser_1 = require("../utils/ASTTraverser");
const security_constants_1 = require("../constants/security.constants");
class InsecureRandomDetector extends BaseDetector_1.BaseDetector {
constructor() {
super("InsecureRandomDetector", "insecure-random", "high", InsecureRandomDetector.RANDOM_PATTERNS);
}
async detect(scanResult) {
const vulnerabilities = [];
// Filter relevant files
// 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;
// Check if file has secure random alternatives
const hasSecureAlternatives = this.detectSecureRandomUsage(content);
// Apply pattern matching
const patternResults = this.applyPatternMatching(content, filePath);
const patternVulnerabilities = this.convertPatternMatchesToVulnerabilities(patternResults, (match) => this.validateRandomMatch(match));
// Apply AST-based analysis for context-aware detection
const sourceFile = scanResult.sourceFiles.get(filePath);
if (sourceFile) {
const astVulnerabilities = this.applyASTAnalysis(sourceFile, filePath, (sf, fp) => this.analyzeASTForInsecureRandom(sf, fp, hasSecureAlternatives));
vulnerabilities.push(...astVulnerabilities);
}
// Adjust confidence based on file context and security usage
const fileContext = this.getFileContext(filePath, content);
for (const vuln of patternVulnerabilities) {
// Increase confidence if file handles sensitive data
if (fileContext.handlesSensitiveData ||
fileContext.riskContexts.includes("authentication")) {
vuln.confidence = "high";
}
// Add metadata about secure alternatives if not already used
if (hasSecureAlternatives.length === 0) {
vuln.metadata = {
...vuln.metadata,
secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES,
note: "Consider using cryptographically secure random generation",
};
}
vuln.confidence = this.adjustConfidenceBasedOnContext(vuln, fileContext);
if (this.validateVulnerability(vuln)) {
vulnerabilities.push(vuln);
}
}
}
return vulnerabilities;
}
/**
* Detect usage of secure random alternatives in the file
*/
detectSecureRandomUsage(content) {
const foundAlternatives = [];
for (const alternative of security_constants_1.SECURE_RANDOM_ALTERNATIVES) {
if (content.includes(alternative)) {
foundAlternatives.push(alternative);
}
}
return foundAlternatives;
}
/**
* Validate if a random pattern match is in a security context
*/
validateRandomMatch(matchResult) {
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 a security context
return this.isInSecurityContext(match.context || "");
}
/**
* AST-based analysis for insecure random usage
*/
analyzeASTForInsecureRandom(sourceFile, filePath, secureAlternatives) {
const vulnerabilities = [];
// Find Math.random() calls
const mathRandomCalls = this.findMathRandomCalls(sourceFile);
for (const randomCall of mathRandomCalls) {
const securityContext = this.analyzeRandomSecurityContext(randomCall, sourceFile);
if (securityContext) {
const location = ASTTraverser_1.ASTTraverser.getNodeLocation(randomCall, sourceFile);
const context = ASTTraverser_1.ASTTraverser.getNodeContext(randomCall, sourceFile);
const code = ASTTraverser_1.ASTTraverser.getNodeText(randomCall, 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(randomCall),
}, securityContext.description, "high", securityContext.confidence, {
securityContext: securityContext.contextType,
variableName: securityContext.variableName,
hasSecureAlternatives: secureAlternatives.length > 0,
secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES,
detectionMethod: "ast-analysis",
});
vulnerabilities.push(vulnerability);
}
}
// Find Date.now() usage for randomness
const dateNowCalls = this.findDateNowRandomUsage(sourceFile);
for (const dateCall of dateNowCalls) {
const location = ASTTraverser_1.ASTTraverser.getNodeLocation(dateCall, sourceFile);
const context = ASTTraverser_1.ASTTraverser.getNodeContext(dateCall, sourceFile);
const code = ASTTraverser_1.ASTTraverser.getNodeText(dateCall, 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(dateCall),
}, "Date.now() used for randomness - this is predictable and insecure for cryptographic purposes", "high", "medium", {
randomnessMethod: "date-based",
secureAlternatives: security_constants_1.SECURE_RANDOM_ALTERNATIVES,
detectionMethod: "ast-analysis",
});
vulnerabilities.push(vulnerability);
}
return vulnerabilities;
}
/**
* Find Math.random() call expressions
*/
findMathRandomCalls(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 === "Math" &&
typescript_1.default.isIdentifier(method) &&
method.text === "random");
}
return false;
});
}
/**
* Find Date.now() usage in random contexts
*/
findDateNowRandomUsage(sourceFile) {
const randomUsages = [];
const callExpressions = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.CallExpression);
for (const callExpr of callExpressions) {
if (typescript_1.default.isPropertyAccessExpression(callExpr.expression)) {
const obj = callExpr.expression.expression;
const method = callExpr.expression.name;
if (typescript_1.default.isIdentifier(obj) &&
obj.text === "Date" &&
typescript_1.default.isIdentifier(method) &&
method.text === "now") {
// Check if it's used in a modulo operation (common for "randomness")
const parent = callExpr.parent;
if (typescript_1.default.isBinaryExpression(parent) &&
parent.operatorToken.kind === typescript_1.default.SyntaxKind.PercentToken) {
randomUsages.push(parent);
}
}
}
}
return randomUsages;
}
/**
* Analyze the security context of a Math.random() call
*/
analyzeRandomSecurityContext(randomCall, sourceFile) {
// Get the variable name this random call is assigned to
const variableName = this.getAssignmentVariableName(randomCall);
// Check if variable name suggests security context
if (variableName && this.isSecurityRelatedVariable(variableName)) {
return {
description: `Math.random() used for security-sensitive variable '${variableName}' - use cryptographically secure random instead`,
confidence: "high",
contextType: "variable-assignment",
variableName,
};
}
// Check function context
const functionName = this.extractFunctionFromAST(randomCall);
if (functionName && this.isSecurityRelatedFunction(functionName)) {
return {
description: `Math.random() used in security-related function '${functionName}' - use cryptographically secure random instead`,
confidence: "high",
contextType: "function-context",
variableName: functionName,
};
}
// Check surrounding code context
const context = ASTTraverser_1.ASTTraverser.getNodeContext(randomCall, sourceFile, 5);
if (this.isInSecurityContext(context)) {
return {
description: "Math.random() used in security context - use cryptographically secure random instead",
confidence: "medium",
contextType: "contextual",
variableName,
};
}
return null;
}
/**
* Get the variable name that a random call is assigned to
*/
getAssignmentVariableName(node) {
let parent = node.parent;
// Walk up to find variable declaration or assignment
while (parent) {
if (typescript_1.default.isVariableDeclaration(parent) && typescript_1.default.isIdentifier(parent.name)) {
return parent.name.text;
}
if (typescript_1.default.isBinaryExpression(parent) &&
parent.operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken &&
typescript_1.default.isIdentifier(parent.left)) {
return parent.left.text;
}
if (typescript_1.default.isPropertyAssignment(parent) && typescript_1.default.isIdentifier(parent.name)) {
return parent.name.text;
}
parent = parent.parent;
}
return undefined;
}
/**
* Check if variable name suggests security context
*/
isSecurityRelatedVariable(name) {
const lowerName = name.toLowerCase();
// First check if it's a UI/visual context (should NOT be flagged)
if (security_constants_1.UI_VISUAL_CONTEXTS.some((context) => lowerName.includes(context))) {
return false;
}
// Then check if it's actually security-related
return security_constants_1.SECURITY_CONTEXTS.some((context) => lowerName.includes(context) ||
lowerName.replace(/[_-]/g, "").includes(context.replace(/[_-]/g, "")));
}
/**
* Check if function name suggests security context
*/
isSecurityRelatedFunction(name) {
const lowerName = name.toLowerCase();
// First check if it's a UI/visual function (should NOT be flagged)
if (security_constants_1.UI_FUNCTION_PATTERNS.some((pattern) => pattern.test(lowerName))) {
return false;
}
// Then check if it's actually security-related
return security_constants_1.SECURITY_FUNCTIONS.some((func) => lowerName.includes(func) &&
security_constants_1.SECURITY_CONTEXTS.some((ctx) => lowerName.includes(ctx)));
}
/**
* Check if context suggests security usage
*/
isInSecurityContext(context) {
const lowerContext = context.toLowerCase();
// Check if it's clearly a UI/visual context (should NOT be flagged)
if (security_constants_1.UI_VISUAL_CONTEXTS.some((uiContext) => lowerContext.includes(uiContext))) {
return false;
}
if (security_constants_1.UI_FUNCTION_PATTERNS.some((pattern) => pattern.test(lowerContext))) {
return false;
}
// Then check if it's actually security-related
return security_constants_1.SECURITY_CONTEXTS.some((secContext) => lowerContext.includes(secContext));
}
/**
* 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.InsecureRandomDetector = InsecureRandomDetector;
InsecureRandomDetector.RANDOM_PATTERNS = [
{
id: "math-random-security",
name: "Math.random() in security context",
description: "Math.random() used in security context - use cryptographically secure random instead",
pattern: {
type: "regex",
expression: /Math\.random\s*\(\s*\)/g,
},
vulnerabilityType: "insecure-random",
severity: "high",
confidence: "medium",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
{
id: "date-now-random",
name: "Date.now() used for randomness",
description: "Date.now() used for randomness - this is predictable and insecure",
pattern: {
type: "regex",
expression: /Date\.now\s*\(\s*\)\s*%/g,
},
vulnerabilityType: "insecure-random",
severity: "high",
confidence: "medium",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
];