Fix CVE-2025-69872 in diskcache¶
Install diskcache-community and read your caches with SafeDisk:
$ pip install diskcache-community
from diskcache_community import Cache, SafeDisk
cache = Cache('/var/tmp/mycache', disk=SafeDisk)
The rest of this page explains what the vulnerability is, who is exposed, the steps for caches you create and for caches libraries create for you, and what a restricted disk has to cover if you write your own.
What the vulnerability is¶
CVE-2025-69872 (CWE-502) affects every release of the upstream
diskcache package, 5.6.3 included, and there is no fixed upstream release.
The default Disk stores keys and values
that are not plain strings, bytes or numbers as pickles, in cache.db and in
the value files next to it, and reads them back with pickle.load(). A
pickle can name any function to call while it is loaded, so anyone who can
write into the cache directory can run code in every process that reads from
it.
The attacker needs write access to the cache directory. You are exposed when someone other than the user running the process can write there, for example:
a cache directory under
/tmpor another directory shared between users;a cache on a shared volume or network drive, or one mounted into a container that other workloads can write to;
a cache restored from a CI cache, an artifact or a download that someone else could have produced.
A cache that only its own user can write to is not exposed. diskcache-community
creates new cache directories with mode 0700 and leaves existing ones as
they are, so check the permissions of directories created by older versions.
Steps¶
Move to diskcache-community. The fork keeps the API and the files on disk, and is imported as
diskcache_community. Migrating from diskcache 5.6.3 covers the dependency, the imports and the Django settings.Select SafeDisk for caches you create. Pass it as
disktoCache,FanoutCache,DequeandIndex, and as theDISKoption ofDjangoCache:CACHES = { 'default': { 'BACKEND': 'diskcache_community.DjangoCache', 'LOCATION': '/var/tmp/django_cache', 'DISK': 'diskcache_community.SafeDisk', } }
SafeDiskreads the same files asDiskbut unpickles only standard-library types such asdict,list,datetimeandDecimal, refuses any other key or value when it is written, and refuses file paths outside the cache directory. SafeDisk in the tutorial lists the types.Select SafeDisk for caches that libraries create. A cache created without a
diskargument uses the process default, which you can set without changing the library’s code:$ DISKCACHE_DISK=safe python app.py
or in code, before the caches are created:
import diskcache_community diskcache_community.configure(diskcache_community.SafeDisk)
This reaches only caches created through
diskcache_community. A library that imports the upstreamdiskcachepackage keeps reading pickles withpickle.load()until it moves to the fork. A library that passes its own disk keeps it. Default disk in the tutorial has the details.Allow your own classes. If the cache holds dataclasses, named tuples, enums or pydantic models, allow them with
SafeDisk.with_typesinstead of falling back toDisk:from diskcache_community import Cache, SafeDisk from myapp.models import Message, Point AppDisk = SafeDisk.with_types(Message, Point, modules=('myapp.schemas',)) cache = Cache('/var/tmp/mycache', disk=AppDisk)
With
DISKCACHE_DISK=safe, the same lists go intoDISKCACHE_SAFE_TYPESandDISKCACHE_SAFE_MODULES. numpy classes cannot be allowed, because a crafted numpy pickle can corrupt memory. UseSafeDisk.with_numpyto store arrays instead, which saves them in numpy’s.npyformat and combines withwith_types:from diskcache_community import Cache, SafeDisk ArrayDisk = SafeDisk.with_numpy().with_types(Message, Point) cache = Cache('/var/tmp/mycache', disk=ArrayDisk) cache['embedding'] = array
To select it from the environment, name a class like
ArrayDiskinDISKCACHE_DISK, such asmyapp.cache:ArrayDisk. The same lists apply.Deal with entries already in the cache. A cache written with the default
Diskmay hold objects thatSafeDiskrefuses; those entries can be cleared or treated as missing (Unreadable entries).Check the result.
type(cache.disk)names the disk a cache uses, anddefault_disk()returns the process default. Runpip show diskcacheto see whether the upstream package is still installed, and, with pipdeptree installed,pipdeptree -r -p diskcacheto see which dependency requires it.
What stays the same¶
The default Disk still reads pickles with
pickle.load(), as 5.6.3 does, so every existing cache keeps opening and
caches of arbitrary objects keep working. SafeDisk is a choice you make per cache or per process,
and the choice is never stored in the cache directory: every process that
opens a directory decides for itself.
JSONDisk writes no pickles, but it
still reads one from a crafted row. If you use it, select SafeJSONDisk, which reads the same files and never
reads a pickle. SafeDisk does not
decode what JSONDisk wrote. See
SafeJSONDisk.
What a restricted disk has to cover¶
Some projects subclass Disk and replace
pickle.load() with a restricted unpickler of their own. Restricting how
values are unpickled is not enough. A disk that closes this vulnerability
covers all of the following, and SafeDisk does each one:
Keys as well as values. Non-trivial keys are pickled too, and unpickled whenever the cache returns a key.
SafeDiskreads keys with the same unpickler as values.The filename column. Large values live in files whose path is stored in
cache.db, and reads, deletes, expiry and culling all follow that path.SafeDiskrefuses any path that leaves the cache directory, on reads and on removal.Exact classes, not module prefixes. A pickle can name a global through a module that merely imports it.
SafeDisk.with_typesaccepts a name only when every step is a class defined in a trusted module, and it refuses dunder attributes.No numpy. Array pickles can corrupt memory.
SafeDisk.with_typesrefuses numpy classes even when asked for them; usewith_numpyto store arrays instead.No imports while reading.
SafeDisklooks classes up only in modules that are already imported.Refuse on write. A value the reader would refuse fails when it is written, with
TypeErrornaming the class, not when a later process reads it.
To report a problem with SafeDisk,
follow SECURITY.md in the repository.