Security

This page contains some security recommendations and guidelines for advanced use cases.

Shared Server Environment

requests-cache is mainly intended for use as a private, client-side, single-user cache, analogous to a browser cache. It should not be used as a public/proxy cache (like Squid or Varnish). For a purely server-side use case (i.e., caching responses to incoming requests), your best option will likely be a purpose-built server cache that integrates with your web framework of choice.

Applications that are both HTTP clients and servers can use requests-cache, but some extra consideration is required. In particular, if your server is shared among multiple principals (users, applications, tenants, etc.), you will need to consider the following:

Removing Sensitive Info

The ignored_parameters option can be used to prevent credentials and other sensitive info from being saved to the cache. It applies to request parameters, body (JSON or form encoding), and headers. Ignored params will be both omitted from request matching and redacted from the cache.

Some are ignored by default, including:

  • Authorization header (most authentication systems)

  • access_token request param (used by OAuth)

  • access_token in POST body (used by OAuth)

  • api_key request param (used by OpenAPI spec)

  • X-API-KEY header (used by OpenAPI spec)

  • X-Access-Token/X-Auth-Token headers (some JWT implementations)

To explicitly ignore + redact a different parameter or header:

>>> from requests_cache import CachedSession
>>> session = CachedSession(ignored_parameters=['X-Nonstandard-Credential'])

To append to the default list instead of replacing it:

>>> from requests_cache import CachedSession, DEFAULT_IGNORED_PARAMS
>>> ignored_parameters = list(DEFAULT_IGNORED_PARAMS) + ['X-Nonstandard-Credential']
>>> session = CachedSession(ignored_parameters=ignored_parameters)

However, there are cases where you would not want to exclude this information from the cache; see section below.

Auth-gated content

Warning

Authenticated requests for multiple users must be handled carefully to avoid exposing authenticated content from one user (or other principal) to an unintended one.

This can happen if there is no Request Matching information available to distinguish one user’s request from another. Since requests-cache only operates at the HTTP level, it has no knowledge of users or other app/framework-specific request context, aside from what is passed via request params and headers.

To protect against the most common cases of this, requests-cache will refuse to serve a cached request containing a header that is in both ignored_parameters and Vary (for example, Authorization + Vary: Authorization).

It’s preferable to handle this proactively in your application, though. Options include:

  • Use a separate cache per user

  • Exclude authenticated requests completely (via any combination of Cache Filtering features)

  • Pass additional headers or params solely for request matching purposes

  • Always match on authentication headers using match_headers and (if applicable) overriding ignored_parameters. If your cache backend is secured, you can choose to not redact auth headers/params from the cache.

Example: In the case of Authorization with a JWT, this would make the tradeoff of storing the token in the cache but making it usable for request matching (i.e., store a separate cached response per token value).

session = CachedSession(
    match_headers=['Authorization'],  # Make this part of the cache key, even if not specified by Vary
    ignored_parameters=[],            # Remove it from default ignored/redacted params
)

Pickle Vulnerabilities

Warning

The python pickle module has known security vulnerabilities, potentially leading to code execution when deserializing data.

This means it should only be used to deserialize data that you trust hasn’t been tampered with.

Safe Pickling With Untrusted Data

If you’re working with untrusted data, consider using one of the other supported Serializers instead of pickle.

Since this isn’t always possible, requests-cache can optionally use itsdangerous to add a layer of security around these operations. It works by signing serialized data with a secret key that you control. Then, if the data is tampered with, the signature check fails and raises an error.

Optional dependencies can be installed with:

pip install itsdangerous

Creating and Storing a Secret Key

To enable this behavior, first create a secret key, which can be any str or bytes object.

One common pattern for handling this is to store it wherever you store the rest of your credentials (Linux keyring, macOS keychain, password database, etc.), set it in an environment variable, and then read it in your application:

>>> import os
>>> secret_key = os.environ['SECRET_KEY']

Alternatively, you can use the keyring package to read the key directly:

>>> import keyring
>>> secret_key = keyring.get_password('requests-cache-example', 'secret_key')

Signing Cached Responses

Once you have your key, create a safe_pickle_serializer() with it:

>>> from requests_cache import CachedSession, safe_pickle_serializer
>>> serializer = safe_pickle_serializer(secret_key=secret_key)
>>> session = CachedSession(serializer=serializer)
>>> session.get('https://httpbin.org/get')

Note

You can also make your own Custom Serializers, if you would like more control over how responses are serialized.

You can verify that it’s working by modifying the cached item (without your key):

>>> serializer_2 = safe_pickle_serializer(secret_key='a different key')
>>> session_2 = CachedSession(serializer=serializer_2)
>>> cache_key = list(session_2.cache.responses.keys())[0]
>>> session_2.cache.responses[cache_key] = 'exploit!'

Then, if you try to get that cached response again (with your key), you will get an error:

>>> session.get('https://httpbin.org/get')
BadSignature: Signature b'iFNmzdUOSw5vqrR9Cb_wfI1EoZ8' does not match