@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
JavaScript
;
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;