UNPKG

@nodesecure/js-x-ray

Version:
168 lines 6.49 kB
// Import Node.js Dependencies import path from "node:path"; // Import Internal Dependencies import { VariableTracer } from "./VariableTracer.js"; import { InlinedRequire } from "./probes/isRequire/InlinedRequire.js"; import { Deobfuscator } from "./Deobfuscator.js"; import { rootLocation, toArrayLocation, isSvg, isStringBase64 } from "./utils/index.js"; import { generateWarning } from "./warnings.js"; import { CollectableSetRegistry } from "./CollectableSetRegistry.js"; // CONSTANTS const kMaximumEncodedLiterals = 10; export class SourceFile { tracer = new VariableTracer().enableDefaultTracing(); inTryStatement = false; dependencyAutoWarning = false; deobfuscator = new Deobfuscator(); dependencies = new Map(); encodedLiterals = new Map(); warnings = []; flags = new Set(); path = new SourceFilePath(); sensitivity; metadata; collectablesSetRegistry; packageName; constructor(sourceLocation, options) { const { metadata, collectableRegistry } = options ?? {}; this.path.use(sourceLocation); this.metadata = metadata; this.collectablesSetRegistry = collectableRegistry ?? new CollectableSetRegistry([]); this.packageName = options?.packageName; } addDependency(name, location, unsafe = this.dependencyAutoWarning) { if (typeof name !== "string" || name.trim() === "") { return; } const dependencyName = name.charAt(name.length - 1) === "/" ? name.slice(0, -1) : name; this.dependencies.set(dependencyName, { unsafe, inTry: this.inTryStatement, ...(location ? { location } : {}) }); if (this.packageName !== dependencyName) { this.collectablesSetRegistry.add("dependency", { value: dependencyName, file: this.path.location, location: toArrayLocation(location ?? undefined), metadata: { ...this.metadata, inTry: this.inTryStatement, unsafe } }); } if (this.dependencyAutoWarning) { this.warnings.push(generateWarning("unsafe-import", { value: dependencyName, location: location || void 0 })); } } addEncodedLiteral(value, location) { if (this.encodedLiterals.size > kMaximumEncodedLiterals) { return; } if (this.encodedLiterals.has(value)) { const index = this.encodedLiterals.get(value); this.warnings[index].location.push(toArrayLocation(location ?? rootLocation())); return; } this.warnings.push(generateWarning("encoded-literal", { value, location })); this.encodedLiterals.set(value, String(this.warnings.length - 1)); } analyzeLiteral(node, inArrayExpr = false) { if (typeof node.value !== "string" || isSvg(node)) { return; } this.deobfuscator.analyzeString(node.value); const hasRawValue = "raw" in node; const hasHexadecimalSequence = hasRawValue ? /\\x[a-fA-F0-9]{2}/g.exec(node.raw) !== null : null; const hasUnicodeSequence = hasRawValue ? /\\u[a-fA-F0-9]{4}/g.exec(node.raw) !== null : null; const isBase64 = isStringBase64(String(node.value), { allowEmpty: false }); if ((hasHexadecimalSequence || hasUnicodeSequence) && isBase64) { if (inArrayExpr) { this.deobfuscator.encodedArrayValue++; } else { this.addEncodedLiteral(node.value, node.loc); } } } getResult(isMinified) { const obfuscatorName = this.deobfuscator.assertObfuscation(); if (obfuscatorName !== null) { this.warnings.push(generateWarning("obfuscated-code", { value: obfuscatorName })); } let filteredLen = 0; let filteredSum = 0; for (const value of this.deobfuscator.identifiers) { if (value.type !== "Property" && typeof value.name === "string") { filteredLen++; filteredSum += value.name.length; } } const idsLengthAvg = filteredLen === 0 ? 0 : filteredSum / filteredLen; const stringScore = sum(this.deobfuscator.literalScores); if (!isMinified && filteredLen > 5 && idsLengthAvg <= 1.5) { this.warnings.push(generateWarning("short-identifiers", { value: String(idsLengthAvg) })); } if (stringScore >= 3) { this.warnings.push(generateWarning("suspicious-literal", { value: String(stringScore) })); } if (this.encodedLiterals.size > kMaximumEncodedLiterals) { this.warnings.push(generateWarning("suspicious-file", { value: null })); this.warnings = this.warnings .filter((warning) => warning.kind !== "encoded-literal"); } return { idsLengthAvg, stringScore, warnings: this.warnings }; } walk(node, callback) { const split = InlinedRequire.split(node); if (split !== null) { this.tracer.walk(split.virtualDeclaration); if (split.rebuildExpression) { this.tracer.walk(split.rebuildExpression); } callback(split.virtualDeclaration); if (split.rebuildExpression) { callback(split.rebuildExpression); } } this.tracer.walk(node); this.deobfuscator.walk(node); // Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause if (node.type === "TryStatement" && node.handler) { this.inTryStatement = true; } else if (node.type === "CatchClause") { this.inTryStatement = false; } callback(node); } } export class SourceFilePath { location = null; use(location) { this.location = location ?? null; } resolve(...parts) { if (this.location === null) { return path.posix.join(...parts); } return path.posix.join(this.location, ...parts); } } function sum(arr = []) { return arr.length === 0 ? 0 : (arr.reduce((prev, curr) => prev + curr, 0) / arr.length); } //# sourceMappingURL=SourceFile.js.map