UNPKG

@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.

107 lines (106 loc) 5.16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FunctionSink = void 0; const node_path_1 = require("node:path"); const AgentSingleton_1 = require("../agent/AgentSingleton"); const Context_1 = require("../agent/Context"); const wrapExport_1 = require("../agent/hooks/wrapExport"); const envToBool_1 = require("../helpers/envToBool"); const getLibraryRoot_1 = require("../helpers/getLibraryRoot"); const getNodeVersion_1 = require("../helpers/getNodeVersion"); const checkContextForJsInjection_1 = require("../vulnerabilities/js-injection/checkContextForJsInjection"); const node_fs_1 = require("node:fs"); const colorText_1 = require("../helpers/colorText"); const warnBox_1 = require("../helpers/warnBox"); const isMusl_1 = require("../helpers/isMusl"); const isCodeGenerationFromStringsDisallowed_1 = require("../helpers/isCodeGenerationFromStringsDisallowed"); class FunctionSink { inspectFunction(args) { if (args.length === 0) { return undefined; } const code = args[0]; if (!code || typeof code !== "string") { return undefined; } const context = (0, Context_1.getContext)(); if (!context) { return undefined; } return (0, checkContextForJsInjection_1.checkContextForJsInjection)({ js: code, operation: "new Function/eval", context, }); } loadNativeAddon() { const majorVersion = (0, getNodeVersion_1.getMajorNodeVersion)(); const arch = process.arch; const platform = process.platform; const nodeInternalsDir = (0, node_path_1.join)((0, getLibraryRoot_1.getLibraryRoot)(), "node_internals"); let binaryPath = (0, node_path_1.join)(nodeInternalsDir, `zen-internals-node-${platform}-${arch}-node${majorVersion}.node`); if ((0, isMusl_1.isMusl)()) { binaryPath = (0, node_path_1.join)(nodeInternalsDir, `zen-internals-node-${platform}-${arch}-musl-node${majorVersion}.node`); } if (!(0, node_fs_1.existsSync)(binaryPath)) { // oxlint-disable-next-line no-console console.warn((0, colorText_1.colorText)("red", (0, warnBox_1.warnBox)(`Zen will NOT block code injection attacks (eval, new Function). Cannot find native addon for Node.js ${majorVersion} on ${platform}-${arch}. Request support: https://github.com/AikidoSec/firewall-node/issues`))); return; } let bindings; try { bindings = require(binaryPath); } catch (error) { // oxlint-disable-next-line no-console console.warn((0, colorText_1.colorText)("red", (0, warnBox_1.warnBox)(`Zen will NOT block code injection attacks (eval, new Function). Failed to load native addon for Node.js ${majorVersion} on ${platform}-${arch}: ${error.message}`))); return; } if (!bindings || typeof bindings.setCodeGenerationCallback !== "function") { // oxlint-disable-next-line no-console console.warn((0, colorText_1.colorText)("red", (0, warnBox_1.warnBox)(`Zen will NOT block code injection attacks (eval, new Function). Native addon for Node.js ${majorVersion} on ${platform}-${arch} is invalid.`))); return; } return bindings; } wrap(_) { if ((0, envToBool_1.envToBool)(process.env.AIKIDO_DISABLE_CODE_GENERATION_HOOK)) { return; } // If Node was started with --disallow-code-generation-from-strings, V8 already // blocks every eval and new Function() call. We use the same V8 hook, so if we // registered our callback it would override that and let eval run again for code // that doesn't come from a request. So we do nothing and let Node keep blocking // everything. No warning needed: we only block eval when the code comes from user // input, and Node already blocks all of it, including those cases. if ((0, isCodeGenerationFromStringsDisallowed_1.isCodeGenerationFromStringsDisallowed)()) { return; } const bindings = this.loadNativeAddon(); if (!bindings) { return; } bindings.setCodeGenerationCallback((code) => { const agent = (0, AgentSingleton_1.getInstance)(); if (!agent) { return; } const context = (0, Context_1.getContext)(); if (!context) { return; } try { (0, wrapExport_1.inspectArgs)([code], this.inspectFunction, context, agent, { name: "Function/eval", type: "global", }, "<compile>", "eval_op"); } catch (error) { // In blocking mode, onInspectionInterceptorResult would throw to block the operation // To block the code generation, we need to return a string that will be used for the thrown error message return error.message; } }); } } exports.FunctionSink = FunctionSink;