@aikidosec/firewall
Version:
Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.
141 lines (140 loc) • 6.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.onModuleLoad = onModuleLoad;
const getModuleInfoFromPath_1 = require("../getModuleInfoFromPath");
const isBuiltinModule_1 = require("../isBuiltinModule");
const getPackageVersionFromPath_1 = require("./getPackageVersionFromPath");
const codeTransformation_1 = require("./codeTransformation");
const instructions_1 = require("./instructions");
const removeNodePrefix_1 = require("../../../helpers/removeNodePrefix");
const AgentSingleton_1 = require("../../AgentSingleton");
const module_1 = require("module");
const processGetBuiltin_1 = require("./processGetBuiltin");
const wrapBuiltinExports_1 = require("./wrapBuiltinExports");
const builtinPatchedSymbol = Symbol("zen.instrumentation.builtin.patched");
function onModuleLoad(path, context, previousLoadResult) {
var _a;
try {
// Ignore unsupported formats, e.g. wasm, native addons or json
if (
// Sometimes the format is not set!
previousLoadResult.format &&
![
"builtin",
"commonjs",
"module",
"commonjs-typescript",
"module-typescript",
].includes(previousLoadResult.format)) {
return previousLoadResult;
}
// The previousLoadResult.format must not be 'builtin' if it e.g. was modified by import-in-the-middle
// import-in-the-middle returns 'module' for builtins that it patched to make the modification of builtins possible
// The returned source is ignored if the format is 'builtin'
const isBuiltin = previousLoadResult.format === "builtin" || (0, isBuiltinModule_1.isBuiltinModule)(path);
// For Node.js builtin modules
if (isBuiltin) {
return patchBuiltin(path, previousLoadResult);
}
if (isSelfCheckImport(path)) {
return updateSelfCheckSource(previousLoadResult);
}
return patchPackage(path, previousLoadResult);
}
catch (error) {
// Do not break the module loading process, just log the error
if (error instanceof Error) {
(_a = (0, AgentSingleton_1.getInstance)()) === null || _a === void 0 ? void 0 : _a.onFailedToWrapModule(path, error);
}
return previousLoadResult;
}
}
function patchPackage(path, previousLoadResult) {
var _a, _b, _c;
const moduleInfo = (0, getModuleInfoFromPath_1.getModuleInfoFromPath)(path);
if (!moduleInfo) {
// This is e.g. the case for user code (not a dependency)
// We don't want to modify user code yet
return previousLoadResult;
}
// Check if the version of the package is supported
const pkgVersion = (0, getPackageVersionFromPath_1.getPackageVersionFromPath)(moduleInfo.base);
if (!pkgVersion) {
// We can't determine the version of the package
return previousLoadResult;
}
(_a = (0, AgentSingleton_1.getInstance)()) === null || _a === void 0 ? void 0 : _a.onPackageRequired(moduleInfo.name, pkgVersion);
if (!(0, instructions_1.shouldPatchPackage)(moduleInfo.name)) {
// We don't want to modify this module
return previousLoadResult;
}
if (!(0, instructions_1.shouldPatchFile)(moduleInfo.name, moduleInfo.path)) {
// We don't want to patch this file
return previousLoadResult;
}
// Right now we only allow one matching instruction set for one file
// So if e.g. multiple version are matching, we only use the first one
const matchingInstructions = (0, instructions_1.getPackageFileInstrumentationInstructions)(moduleInfo.name, pkgVersion, moduleInfo.path);
// Report to the agent that the package was wrapped or not if it's version is not supported
(_b = (0, AgentSingleton_1.getInstance)()) === null || _b === void 0 ? void 0 : _b.onPackageWrapped(moduleInfo.name, {
version: pkgVersion,
supported: !!matchingInstructions,
});
if (!matchingInstructions) {
// We don't want to patch this package version or file
return previousLoadResult;
}
const pkgLoadFormat = (_c = previousLoadResult.format) !== null && _c !== void 0 ? _c : "unambiguous";
const sourceString = typeof previousLoadResult.source === "string"
? previousLoadResult.source
: new TextDecoder("utf-8").decode(previousLoadResult.source);
const newSource = (0, codeTransformation_1.transformCode)(moduleInfo.name, pkgVersion, path, sourceString, pkgLoadFormat, matchingInstructions);
// Prevent returning empty or undefined source text
if (!newSource) {
return previousLoadResult;
}
return {
format: previousLoadResult.format,
shortCircuit: previousLoadResult.shortCircuit,
source: newSource,
};
}
function patchBuiltin(builtinName, previousLoadResult) {
const builtinNameWithoutPrefix = (0, removeNodePrefix_1.removeNodePrefix)(builtinName);
const builtin = (0, instructions_1.shouldPatchBuiltin)(builtinNameWithoutPrefix);
if (!builtin) {
return previousLoadResult;
}
let orig = (0, processGetBuiltin_1.getBuiltinModuleWithoutPatching)(builtinName);
if (!orig) {
return previousLoadResult;
}
if (orig[builtinPatchedSymbol]) {
// The builtin module has already been patched, so we don't need to do it again
return previousLoadResult;
}
const newExports = (0, wrapBuiltinExports_1.wrapBuiltinExports)(builtinNameWithoutPrefix, orig);
if (!newExports) {
return previousLoadResult;
}
orig = newExports;
(0, module_1.syncBuiltinESMExports)();
// Mark the builtin as patched to avoid double patching
orig[builtinPatchedSymbol] = true;
return previousLoadResult;
}
function isSelfCheckImport(path) {
// We can't use getModuleInfoFromPath as it would not work in unit tests
return path
.replace(/\\/g, "/")
.includes("hooks/instrumentation/zenHooksCheckImport."); // .js or .ts
}
function updateSelfCheckSource(previousLoadResult) {
const sourceString = typeof previousLoadResult.source === "string"
? previousLoadResult.source
: new TextDecoder("utf-8").decode(previousLoadResult.source);
return {
...previousLoadResult,
source: sourceString.replace("//SELF_CHECK_REPLACE", ":)"),
};
}