Skip to main content

HardenPlugin

HardenPlugin

The HardenPlugin hardens the Shop and Admin GraphQL APIs against attacks and abuse.

  • It analyzes the complexity on incoming graphql queries and rejects queries that are too complex and could be used to overload the resources of the server.
  • It disables dev-mode API features such as introspection and the GraphQL playground app.
  • It removes field name suggestions to prevent trial-and-error schema sniffing.

It is a recommended plugin for all production configurations.

Installation

yarn add @vendure/harden-plugin

or

npm install @vendure/harden-plugin

Then add the HardenPlugin, calling the .init() method with HardenPluginOptions:

Example

import { HardenPlugin } from '@vendure/harden-plugin';

const config: VendureConfig = {
// Add an instance of the plugin to the plugins array
plugins: [
HardenPlugin.init({
maxQueryComplexity: 650,
apiMode: process.env.APP_ENV === 'dev' ? 'dev' : 'prod',
}),
],
};

Setting the max query complexity

The maxQueryComplexity option determines how complex a query can be. The complexity of a query relates to how many, and how deeply-nested are the fields being selected, and is intended to roughly correspond to the amount of server resources that would be required to resolve that query.

The goal of this setting is to prevent attacks in which a malicious actor crafts a very complex query in order to overwhelm your server resources. Here's an example of a request which would likely overwhelm a Vendure server:

query EvilQuery {
products {
items {
collections {
productVariants {
items {
product {
collections {
productVariants {
items {
product {
variants {
name
}
}
}
}
}
}
}
}
}
}
}
}

This evil query has a complexity score of 2,443,203 - much greater than the default of 1,000!

The complexity score is calculated by the graphql-query-complexity library, and by default uses the defaultVendureComplexityEstimator, which is tuned specifically to the Vendure Shop API.

caution

Note: By default, if the "take" argument is omitted from a list query (e.g. the products or collections query), a default factor of 1000 is applied.

The optimal max complexity score will vary depending on:

  • The requirements of your storefront and other clients using the Shop API
  • The resources available to your server

You should aim to set the maximum as low as possible while still being able to service all the requests required. This will take some manual tuning. While tuning the max, you can turn on the logComplexityScore to get a detailed breakdown of the complexity of each query, as well as how that total score is derived from its child fields:

Example

import { HardenPlugin } from '@vendure/harden-plugin';

const config: VendureConfig = {
// A detailed summary is logged at the "debug" level
logger: new DefaultLogger({ level: LogLevel.Debug }),
plugins: [
HardenPlugin.init({
maxQueryComplexity: 650,
logComplexityScore: true,
}),
],
};

With logging configured as above, the following query:

query ProductList {
products(options: { take: 5 }) {
items {
id
name
featuredAsset {
preview
}
}
}
}

will log the following breakdown:

debug 16/12/22, 14:12 - [HardenPlugin] Calculating complexity of [ProductList]
debug 16/12/22, 14:12 - [HardenPlugin] Product.id: ID! childComplexity: 0, score: 1
debug 16/12/22, 14:12 - [HardenPlugin] Product.name: String! childComplexity: 0, score: 1
debug 16/12/22, 14:12 - [HardenPlugin] Asset.preview: String! childComplexity: 0, score: 1
debug 16/12/22, 14:12 - [HardenPlugin] Product.featuredAsset: Asset childComplexity: 1, score: 2
debug 16/12/22, 14:12 - [HardenPlugin] ProductList.items: [Product!]! childComplexity: 4, score: 20
debug 16/12/22, 14:12 - [HardenPlugin] Query.products: ProductList! childComplexity: 20, score: 35
verbose 16/12/22, 14:12 - [HardenPlugin] Query complexity [ProductList]: 35
Signature
class HardenPlugin {
static options: HardenPluginOptions;
init(options: HardenPluginOptions) => ;
}

options

init

method
(options: HardenPluginOptions) =>