UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks

133 lines (132 loc) 5.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQL = void 0; const Context_1 = require("../agent/Context"); const isPlainObject_1 = require("../helpers/isPlainObject"); const extractInputsFromDocument_1 = require("./graphql/extractInputsFromDocument"); const extractTopLevelFieldsFromDocument_1 = require("./graphql/extractTopLevelFieldsFromDocument"); const isGraphQLOverHTTP_1 = require("./graphql/isGraphQLOverHTTP"); const shouldRateLimitOperation_1 = require("./graphql/shouldRateLimitOperation"); const wrapExport_1 = require("../agent/hooks/wrapExport"); class GraphQL { discoverGraphQLSchema(context, executeArgs, agent) { if (!this.graphqlModule) { return; } if (!executeArgs.schema) { return; } if (!context.method || !context.route) { return; } if (!agent.hasGraphQLSchema(context.method, context.route)) { try { const schema = this.graphqlModule.printSchema(executeArgs.schema); agent.onGraphQLSchema(context.method, context.route, schema); } catch { // Ignore errors } } } discoverGraphQLQueryFields(context, executeArgs, agent) { if (!context.method || !context.route) { return; } const topLevelFields = (0, extractTopLevelFieldsFromDocument_1.extractTopLevelFieldsFromDocument)(executeArgs.document, executeArgs.operationName ? executeArgs.operationName : undefined); if (topLevelFields) { agent.onGraphQLExecute(context.method, context.route, topLevelFields.type, topLevelFields.fields.map((field) => field.name.value)); } } inspectGraphQLExecute(args, agent) { if (!Array.isArray(args) || typeof args[0] !== "object" || !this.graphqlModule) { return; } const executeArgs = args[0]; const context = (0, Context_1.getContext)(); if (!context) { // We expect the context to be set by the wrapped http server return; } if (context && context.method && context.route && (0, isGraphQLOverHTTP_1.isGraphQLOverHTTP)(context)) { // We only want to discover GraphQL over HTTP // We should ignore queries coming from a GraphQL client in SSR mode this.discoverGraphQLSchema(context, executeArgs, agent); this.discoverGraphQLQueryFields(context, executeArgs, agent); } const userInputs = (0, extractInputsFromDocument_1.extractInputsFromDocument)(executeArgs.document, this.graphqlModule.visit); if (executeArgs.variableValues && typeof executeArgs.variableValues === "object") { for (const value of Object.values(executeArgs.variableValues)) { if (typeof value === "string") { userInputs.push(value); } } } if (Array.isArray(context.graphql)) { (0, Context_1.updateContext)(context, "graphql", context.graphql.concat(userInputs)); } else { (0, Context_1.updateContext)(context, "graphql", userInputs); } } handleRateLimiting(args, origReturnVal, agent) { const context = (0, Context_1.getContext)(); if (!context || !agent || !this.graphqlModule) { return origReturnVal; } if (!Array.isArray(args) || !(0, isPlainObject_1.isPlainObject)(args[0])) { return origReturnVal; } const result = (0, shouldRateLimitOperation_1.shouldRateLimitOperation)(agent, context, args[0]); if (result.block) { // Mark the request as rate limited in the context (0, Context_1.updateContext)(context, "rateLimitedEndpoint", result.endpoint); return { errors: [ new this.graphqlModule.GraphQLError("You are rate limited by Zen.", { nodes: [result.field], extensions: { code: "RATE_LIMITED_BY_ZEN", ipAddress: context.remoteAddress, }, }), ], }; } return origReturnVal; } wrapExecution(exports, pkgInfo) { const methods = ["execute", "executeSync"]; for (const method of methods) { (0, wrapExport_1.wrapExport)(exports, method, pkgInfo, { kind: "graphql_op", modifyReturnValue: (args, returnValue, agent) => this.handleRateLimiting(args, returnValue, agent), inspectArgs: (args, agent) => this.inspectGraphQLExecute(args, agent), }); } } wrap(hooks) { hooks .addPackage("graphql") .withVersion("^16.0.0") .onFileRequire("execution/execute.js", (exports, pkgInfo) => { this.wrapExecution(exports, pkgInfo); }) .onRequire((exports) => { this.graphqlModule = exports; }); hooks .addPackage("@graphql-tools/executor") .withVersion("^1.0.0") .onFileRequire("cjs/execution/execute.js", (exports, pkgInfo) => { this.wrapExecution(exports, pkgInfo); }); } } exports.GraphQL = GraphQL;