UNPKG

@nodesecure/js-x-ray

Version:
117 lines 3.83 kB
// Import Internal Dependencies import { getCallExpressionIdentifier, isStringLiteral } from "../estree/index.js"; import { VariableTracer } from "../VariableTracer.js"; import { CALL_EXPRESSION_DATA } from "../contants.js"; import { rootLocation, toArrayLocation } from "../utils/toArrayLocation.js"; import { generateWarning } from "../warnings.js"; // CONSTANTS const kSensitiveModules = new Set(["os", "dns"]); const kSensitiveMethods = [ "os.userInfo", "os.networkInterfaces", "os.cpus", "dns.getServers" ]; const sensitivePathRegex = /~\/\.(ssh|aws|npmrc|gitconfig|bashrc)(\/[^\s"'`]+)?/; function validateJSONStringify(node, ctx) { if (ctx.sourceFile.sensitivity === "aggressive") { return [false]; } if (ctx.context?.[CALL_EXPRESSION_DATA]?.identifierOrMemberExpr !== "JSON.stringify") { return [false]; } const castedNode = node; if (castedNode.arguments.length === 0) { return [false]; } return [true]; } function validateLiteral(node, ctx) { if (isStringLiteral(node) && sensitivePathRegex.test(node.value)) { ctx.setEntryPoint("literal"); return [true]; } return [false]; } function sensitiveLiteralHandler(node, ctx) { addInContext(node.value, node.loc, ctx); } function sensitiveMethodsHandler(node, ctx) { const { sourceFile } = ctx; const firstArg = node.arguments[0]; if (firstArg.type !== "CallExpression") { return; } const id = getCallExpressionIdentifier(firstArg); if (!id) { return; } const data = sourceFile.tracer.getDataFromIdentifier(id); if (kSensitiveMethods.some((method) => data?.identifierOrMemberExpr === method && sourceFile.tracer.importedModules.has(method.split(".")[0]))) { addInContext(data?.identifierOrMemberExpr, firstArg.loc, ctx); } } function addInContext(value, loc, ctx) { const arrayLocation = ctx.context?.[value]; if (arrayLocation) { arrayLocation.push(toArrayLocation(loc ?? rootLocation())); } else { ctx.context[value] = [toArrayLocation(loc ?? rootLocation())]; } } function initialize(ctx) { const { sourceFile, context } = ctx; const { tracer } = sourceFile; tracer .trace("JSON.stringify", { followConsecutiveAssignment: true }) .trace("os.userInfo", { moduleName: "os", followConsecutiveAssignment: true }) .trace("os.networkInterfaces", { moduleName: "os", followConsecutiveAssignment: true }) .trace("os.cpus", { moduleName: "os", followConsecutiveAssignment: true }) .trace("dns.getServers", { moduleName: "dns", followConsecutiveAssignment: true }); if (sourceFile.sensitivity !== "aggressive") { return; } tracer.on(VariableTracer.ImportEvent, ({ moduleName, location }) => { if (kSensitiveModules.has(moduleName) && !(moduleName in context)) { context[moduleName] = [toArrayLocation(location ?? undefined)]; } }); } function finalize(ctx) { const { sourceFile, context } = ctx; if (context && Object.keys(context).length > 0) { const warning = generateWarning("data-exfiltration", { value: Object.keys(context).join(", ") }); sourceFile.warnings.push({ ...warning, location: Object.values(context).flat() }); } } const dateExifiltration = { name: "dataExfiltration", nodeTypes: ["CallExpression", "Literal"], validateNode: [validateJSONStringify, validateLiteral], initialize, finalize, main: { default: sensitiveMethodsHandler, literal: sensitiveLiteralHandler }, breakOnMatch: false, context: {} }; export default dateExifiltration; //# sourceMappingURL=data-exfiltration.js.map