Since 1.25.0

Safelisting with Persisted Queries

Secure your graph while minimizing request latency


This feature is only available with a GraphOS Enterprise plan. You can test it out by signing up for a free GraphOS trial. To compare GraphOS feature support across all plan types, see the pricing page.

With GraphOS Enterprise, you can enhance your supergraph's security by maintaining a persisted query list (PQL) for your GraphOS Router. To create and update the PQL, first-party apps register trusted operations to the PQL at build time.

 note
Clients can register any kind of operation to a PQL, including queries, mutations, and subscriptions.

At runtime, the router checks incoming requests against the PQL, which can act as operation safelist, depending on your router configuration.

Your router can use its persisted query list (PQL) to both protect your supergraph and speed up your clients' operations:

  • When you enable safelisting, your router rejects any incoming operations not registered in its PQL.

  • Client apps can execute an operation by providing its PQL-specified ID instead of the entire operation string.

    • Requesting by ID can significantly reduce latency and bandwidth usage for large operation strings.

    • Your router can require that clients provide operations by ID and reject full operation strings—even operation strings present in the PQL.

Differences from automatic persisted queries

The Apollo Router Core also supports a related feature called automatic persisted queries (APQ). With APQ, clients can execute a GraphQL operation by sending the SHA256 hash of its operation string instead of the entire string. APQ doesn't support safelisting because the router updates its APQ cache over time with any operations it receives.

For more details on differences between APQ and this feature, see the GraphOS persisted queries documentation.

Implementation

Enabling operation safelisting has a few steps:

  1. PQL creation and linking

  2. Router configuration

  3. Operation registration

  4. Client updates

This article details the router configuration step. For more information on other configuration aspects, see the GraphOS persisted queries documentation.

Router configuration

As soon as a graph variant has a linked PQL, you can configure router instances to fetch and use the PQL by following these steps:

  1. Ensure your router instances are ready to work with PQLs:

  2. Set your desired security level in your router's YAML config file. For supported options, see router security levels. When first implementing persisted queries, it's best to start with audit—or "dry run"—mode.

  3. Deploy your updated router instances to begin using your PQL.

Once your organization's PQL has registered all your clients' operations and you've ensured your client apps are only sending registered operations, you can update your router configuration to the safelisting security level.

Router security levels

The GraphOS Router supports the following security levels, in increasing order of restrictiveness:

  • Allow operation IDs: Clients can optionally execute an operation by providing the operation's PQL-specified ID.

    • All other levels also provide this core capability.

    • This level doesn't provide safelisting.

  • Audit mode: Executing operations by providing a PQL-specified ID is still optional, but the router also logs any unregistered operations.

    • The level serves as a dry run and helps you identify operations you may still need to register before turning on safelisting.

  • Safelisting: The router rejects any incoming operations not present in its PQL. Requests can use either ID or operation string.

    • Before moving to this security level, ensure all your client operations are present in your PQL.

  • Safelisting with IDs only: The router rejects any freeform GraphQL operations. Clients can only execute operations by providing their PQL-specified IDs.

    • Before moving to this security level, ensure all your clients execute operations by providing their PQL-specified ID.

When adopting persisted queries, you should start with a less restrictive security such as audit mode. You can then enable increasingly restrictive levels after your teams have updated all clients.

See below for sample YAML configurations for each level. Refer to the router configuration options for option details.

 note
From version 1.25.0 to 1.32.0, the persisted_queries configuration option was named preview_persisted_queries. Upgrade your router to version 1.32.0 or later to use the generally available version of the feature and the example configuration snippets below.

Allow operation IDs

To use persisted queries only to reduce network bandwidth and latency (not for safelisting), add the following minimal configuration:

YAML
router.yaml
1persisted_queries:
2  enabled: true
 note
You can use this security level with or without automatic persisted queries enabled.

This mode lets clients execute operations by providing their PQL-specified ID instead of the full operation string. Your router also continues to accept full operation strings, even for operations that don't appear in its PQL.

Audit mode (dry run)

Turning on logging is crucial for gauging your client apps' readiness for safelisting. The logs identify which operations you need to either add to your PQL or stop your client apps from making.

To enable logging for unregistered queries, enable the log_unknown property:

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  log_unknown: true
 note
You can use audit mode with or without automatic persisted queries enabled.

Unregistered operations appear in your router's logs.

For example:

Text
12023-08-02T11:51:59.833534Z  WARN [trace_id=5006cef73e985810eb086e5900945807] unknown operation operation_body="query ExampleQuery {\n  me {\n    id\n  }\n}\n"

If your router receives an operation registered in the PQL, no log message will be output.

You can use these router logs to audit operations sent to your router and ask client teams to add new ones to your PQL if necessary.

Safelisting

⚠️ caution
Before applying this configuration, ensure your PQL contains all GraphQL operations that all active versions of your clients execute. If you enable safelisting without ensuring this, your router will reject any unpublished client operations.

With the following configuration, your router allows only GraphQL operations that are present in its PQL while rejecting all other operations:

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  log_unknown: true
4  safelist:
5    enabled: true
6    require_id: false
7apq:
8  enabled: false # APQ must be turned off
 note
To enable safelisting, you must turn off automatic persisted queries (APQs). APQs let clients register arbitrary operations at runtime while safelisting restricts operations to those that have been explicitly registered.

To execute an operation, clients can provide its PQL-specified ID or full operation string. The router rejects unregistered operations, and if log_unknown is true, those operations appear in your router's logs.

💡 tip
It's best to keep log_unknown as true while adopting safelisting so you can monitor the operations your router rejects. Once you're confident that all your clients are properly configured, you can turn it off to reduce noise in your logs.

Safelisting with IDs only

⚠️ caution
Do not start with this configuration. It requires all your clients to execute operations by providing their PQL-specified ID. If any clients still provide full operation strings, the router rejects those operations, even if they're included in the safelist.

With the following configuration, your router rejects all operation strings and only accepts registered operation IDs:

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  log_unknown: true
4  safelist:
5    enabled: true
6    require_id: true
7apq:
8  enabled: false # APQ must be turned off
 note
To enable safelisting, you must turn off automatic persisted queries (APQs). APQs let clients register arbitrary operations at runtime while safelisting restricts operations to those that have been explicitly registered.

If you want to use this security level, you should always first set up safelisting with operation strings allowed. ID-only safelisting requires all your clients to execute operations via PQL-specified ID instead of an operation string. While making those necessary changes, you can use the less restrictive safelisting mode in your router.

With log_unknown set to true, the router logs all rejected operations, including those registered to your PQL but that used the full operation string rather than the PQL-specified ID.

 note
It's best to keep log_unknown as true while adopting safelisting so you can monitor the operations your router rejects. Once you're confident that all your clients are properly configured, you can turn it off to reduce noise in your logs.

Configuration options

The router provides four configuration options that you can combine to create the recommended security levels. This section details each configuration option. Refer to the security levels section for recommended combinations.

 note
From version 1.25.0 to 1.32.0, the persisted_queries configuration option was named preview_persisted_queries. Upgrade your router to version 1.32.0 or later to use the generally available version of the feature and the example configuration snippets below.

persisted_queries

This base configuration enables the feature. All other configuration options build off this one.

YAML
router.yaml
1persisted_queries:
2  enabled: true

log_unknown

Adding log_unknown: true to persisted_queries configures the router to log any incoming operations not registered to the PQL.

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  log_unknown: true

If used with the safelist option, the router logs unregistered and rejected operations. With safelist.required_id off, the only rejected operations are unregistered ones. If safelist.required_id is turned on, operations can be rejected even when registered because they use operation IDs rather than operation strings.

experimental_prewarm_query_plan_cache

This feature is experimental. Your questions and feedback are highly valued—don't hesitate to get in touch with your Apollo contact.

By default, the router prewarms the query plan cache using all operations on the PQL when a new schema is loaded, but not at startup. Using the experimental_prewarm_query_plan_cache option, you can tell the router to prewarm the cache using the PQL on startup as well, or tell it not to prewarm the cache when reloading the schema. (This does not affect whether the router prewarms the query plan cache with recently-used operations from its in-memory cache.) Prewarming the cache means can reduce request latency by ensuring that operations are pre-planned when requests are received, but can make startup or schema reloads slower.

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  experimental_prewarm_query_plan_cache:
4    on_startup: true   # default: false
5    on_reload: false   # default: true

experimental_local_manifests

This feature is experimental. Your questions and feedback are highly valued—don't hesitate to get in touch with your Apollo contact.

Adding experimental_local_manifests to your persisted-queries configuration lets you use local persisted query manifests instead of the hosted Uplink version. This is helpful when you're using an offline Enterprise license and can't use Uplink. With the experimental_local_manifests, the router doesn't reload the manifest from the file system, so you need to restart the router to apply changes.

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  experimental_local_manifests: 
4    - ./path/to/persisted-query-manifest.json

You can download a version of your manifest to use locally from GraphOS Studio. Open the PQL page for a graph by clicking the Go to persisted query lists to the left of the graph's name. Then, click the ••• menu under the Actions column to download a PQL's manifest as a JSON file. Save this file locally and update your experimental_local_manifests configuration with the path the file.

safelist

Adding safelist: true to persisted_queries causes the router to reject any operations that haven't been registered to your PQL.

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  safelist:
4    enabled: true
5apq:
6  enabled: false
 note
To enable safelisting, you must turn off automatic persisted queries (APQs). APQs let clients register arbitrary operations at runtime while safelisting restricts operations to those that have been explicitly registered.

By default, the require_id suboption is false, meaning the router accepts both operation IDs and operation strings as long as the operation is registered.

require_id

Adding require_id: true to the safelist option causes the router to reject any operations that either:

  • haven't been registered to your PQL

  • use a full operation string rather than the operation ID

YAML
router.yaml
1persisted_queries:
2  enabled: true
3  safelist:
4    enabled: true
5    require_id: true
6apq:
7  enabled: false
 note
To enable safelisting, you must turn off automatic persisted queries (APQs). APQs let clients register arbitrary operations at runtime while safelisting restricts operations to those that have been explicitly registered.

Limitations

  • Unsupported with offline license. An GraphOS Router using an offline Enterprise license cannot use safelisting with persisted queries. The feature relies on Apollo Uplink to fetch persisted query manifests, so it doesn't work as designed when the router is disconnected from Uplink.

Feedback

Edit on GitHub

Forums