Skip to content

Commit

Permalink
Add cache: "bounded" configuration option (#6536)
Browse files Browse the repository at this point in the history
This commit introduces the `cache: "bounded"` option. AS3 has an unbounded cache
by default, which means that a malicious client can take an open-ended amount of
memory in the cache, crashing the server.

Rather than breaking what has been the status quo since the beginning of the
project, we've chosen to add an opt-in option in order to use a bounded cache
with very little configuration. Similar to `csrfPrevention`, we've updated all
examples in our docs to use this `bounded` option.

This option will go away in AS4 when a bounded cache becomes the default.
  • Loading branch information
trevor-scheer committed Jun 15, 2022
1 parent 67d9036 commit f66fddc
Show file tree
Hide file tree
Showing 33 changed files with 205 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The version headers in this history reflect the versions of Apollo Server itself

- Remove internal dependency on `apollo-server-caching`, switch over to `@apollo/utils.keyvaluecache`. This PR specifically also introduces Keyv as an unbounded cache solution, but will replace with our own simple implementation in a follow-up PR targeting this minor version release. [PR #6522](https://github.com/apollographql/apollo-server/pull/6522)
- Remove dependency on `keyv`/`@apollo/utils.keyvadapter` in favor of a simple `Map`-backed cache which implements TTL [PR #6535](https://github.com/apollographql/apollo-server/pull/6535)
- Add `cache: "bounded"` configuration option, allowing users to opt into bounded request cache (recommended) [PR #6536](https://github.com/apollographql/apollo-server/pull/6536)

## v3.8.2

Expand Down
21 changes: 21 additions & 0 deletions docs/source/api/apollo-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true, // highly recommended
cache: 'bounded',
});
```

Expand Down Expand Up @@ -228,6 +229,25 @@ Available in Apollo Server v3.4.0 and later.
</td>
</tr>

<tr>
<td>

##### `cache`

`KeyValueCache | "bounded"`
</td>
<td>

A `KeyValueCache` which Apollo Server uses for a number of features like APQs and full response caching. This cache is also provided to DataSources and plugins.

By default, the cache is unbounded. We don't recommend this, since a malicious client can run your server out of memory and cause it to crash by filling it with APQs.

If you don't want to configure your own cache, you should set `cache: "bounded"`. The bounded cache is an [`InMemoryLRUCache`](https://www.npmjs.com/package/@apollo/utils.keyvaluecache) with a default size of roughly 30MiB.

FIXME: link to new cache backend page
</td>
</tr>


<tr>
<td colspan="2">
Expand Down Expand Up @@ -726,6 +746,7 @@ async function startApolloServer() {
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/plugin/cache-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginCacheControl({
// Cache everything for 1 second by default.
Expand All @@ -42,6 +43,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [ApolloServerPluginCacheControlDisabled()],
});
```
Expand Down
1 change: 1 addition & 0 deletions docs/source/api/plugin/drain-http-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async function startApolloServer() {
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/plugin/inline-trace.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginInlineTrace({
rewriteError: (err) => err.message.match(SENSITIVE_REGEX) ? null : err,
Expand All @@ -39,6 +40,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [ApolloServerPluginInlineTraceDisabled()],
});
```
Expand Down
10 changes: 7 additions & 3 deletions docs/source/api/plugin/landing-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@ To configure these default plugins while still using same `NODE_ENV`-based logic

```js
import { ApolloServer } from "apollo-server";
import { ApolloServerPluginLandingPageLocalDefault,
ApolloServerPluginLandingPageProductionDefault
import {
ApolloServerPluginLandingPageLocalDefault,
ApolloServerPluginLandingPageProductionDefault
} from "apollo-server-core";

const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
// Install a landing page plugin based on NODE_ENV
process.env.NODE_ENV === 'production'
process.env.NODE_ENV === "production"
? ApolloServerPluginLandingPageProductionDefault({
graphRef: "my-graph-id@my-graph-variant",
footer: false,
Expand Down Expand Up @@ -428,6 +430,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground(),
],
Expand Down Expand Up @@ -512,6 +515,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginLandingPageDisabled(),
],
Expand Down
1 change: 1 addition & 0 deletions docs/source/api/plugin/schema-reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginSchemaReporting(),
],
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/plugin/usage-reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginUsageReporting({
fieldLevelInstrumentation: 0.5,
Expand Down Expand Up @@ -454,6 +455,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [ApolloServerPluginUsageReportingDisabled()],
});
```
Expand Down
1 change: 1 addition & 0 deletions docs/source/builtin-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
// Sets a non-default option on the usage reporting plugin
ApolloServerPluginUsageReporting({
Expand Down
1 change: 1 addition & 0 deletions docs/source/data/data-sources.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
dataSources: () => {
return {
moviesAPI: new MoviesAPI(),
Expand Down
5 changes: 5 additions & 0 deletions docs/source/data/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
formatError: (err) => {
// Don't give the specific errors to the client.
if (err.message.startsWith('Database Error: ')) {
Expand Down Expand Up @@ -398,6 +399,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginUsageReporting({
rewriteError(err) {
Expand Down Expand Up @@ -429,6 +431,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginUsageReporting({
rewriteError(err) {
Expand Down Expand Up @@ -467,6 +470,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
plugins: [
ApolloServerPluginUsageReporting({
rewriteError(err) {
Expand Down Expand Up @@ -512,6 +516,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
plugins: [setHttpPlugin],
});
```
2 changes: 2 additions & 0 deletions docs/source/data/file-uploads.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ async function startServer() {
resolvers,
// Using graphql-upload without CSRF prevention is very insecure.
csrfPrevention: true,
cache: 'bounded',
});
await server.start();

Expand Down Expand Up @@ -181,6 +182,7 @@ const start = async () => {
resolvers,
// Using graphql-upload without CSRF prevention is very insecure.
csrfPrevention: true,
cache: 'bounded',
});

// Start Apollo Server
Expand Down
15 changes: 13 additions & 2 deletions docs/source/data/resolvers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ const resolvers = {

// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: true });
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});

// Launch the server
server.listen().then(({ url }) => {
Expand Down Expand Up @@ -323,7 +328,12 @@ const resolvers = {

// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: true });
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});

// Launch the server
server.listen().then(({ url }) => {
Expand Down Expand Up @@ -388,6 +398,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
context: ({ req }) => ({
authScope: getScope(req.headers.authorization)
})
Expand Down
3 changes: 3 additions & 0 deletions docs/source/data/subscriptions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ To run both an Express app _and_ a separate WebSocket server for subscriptions,
const server = new ApolloServer({
schema,
csrfPrevention: true,
cache: "bounded",
});
```

Expand All @@ -108,6 +109,7 @@ To run both an Express app _and_ a separate WebSocket server for subscriptions,
const server = new ApolloServer({
schema,
csrfPrevention: true,
cache: "bounded",
plugins: [
// Proper shutdown for the HTTP server.
ApolloServerPluginDrainHttpServer({ httpServer }),
Expand Down Expand Up @@ -168,6 +170,7 @@ const serverCleanup = useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
csrfPrevention: true,
cache: "bounded",
plugins: [
// Proper shutdown for the HTTP server.
ApolloServerPluginDrainHttpServer({ httpServer }),
Expand Down
7 changes: 6 additions & 1 deletion docs/source/deployment/azure-functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ const resolvers = {
};

// Create our server.
const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: true });
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});
exports.graphqlHandler = server.createHandler();
```

Expand Down
2 changes: 2 additions & 0 deletions docs/source/deployment/gcp-functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});

exports.handler = server.createHandler();
Expand Down Expand Up @@ -164,6 +165,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
context: ({ req, res }) => ({
headers: req.headers,
req,
Expand Down
8 changes: 7 additions & 1 deletion docs/source/deployment/lambda.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ const resolvers = {
},
};

const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: true });
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});

exports.graphqlHandler = server.createHandler();
```
Expand Down Expand Up @@ -211,6 +216,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
context: ({ event, context, express }) => ({
headers: event.headers,
functionName: context.functionName,
Expand Down
1 change: 1 addition & 0 deletions docs/source/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
});

// The `listen` method launches a web server.
Expand Down
Loading

0 comments on commit f66fddc

Please sign in to comment.