Response caching is one of the first things a team switches on when a self-hosted API starts to feel slow. In Directus, the open-source headless data platform, that one switch can quietly turn a private share link into a public one. A flaw disclosed this week, tracked as CVE-2026-61836 and rated 8.6, means that when response caching is enabled Directus can serve one visitor's permission-filtered data to a completely different visitor. It can also hand out password-protected share data to people who never entered the password. The fix ships in version 12.0.0.
The bug is not in the permission engine. Permissions are evaluated correctly the first time a request comes in. The problem is what Directus does next: it files the result under a cache key that leaves out who asked for it.
How a shared cache key erases the permission check
When response caching is on (CACHE_ENABLED=true), Directus builds each cache key from the API version, the request path, the query string, and the caller's user ID, and nothing else. A logged-in account supplies a user ID, so its cached copy stays pinned to that account. Share links and anonymous callers supply none. Directus mints a share token with no user identity attached, so a share request and a plain anonymous request both collapse to the same empty user value. From the cache's point of view they are the same caller.
Same URL, same query, same empty user means the same bucket, whichever share or permission scope actually produced the answer. Whoever gets there first loads that bucket with a filtered, scoped payload. Everyone who follows and hashes to that key is handed the stored copy, and the permission logic never runs a second time. The share, the role, the policies, the admin and app flags, the whole authorization context that was meant to keep those answers apart, was never written into the key. In vulnerability-classification terms this is CWE-524 and CWE-639: sensitive data cached under a key an unauthorized party can also produce.
Three separate trust boundaries collapse onto one bucket:
Who is actually exposed
Caching is off by default, so a stock install is not affected. The exposure is the intersection of two deliberate choices: you enabled response caching for performance, and you use share links to hand specific records to outside parties. That combination is common on exactly the instances where it hurts most, a tuned production data platform serving external clients, collaborators, or partners through shares. Self-hosted data platforms concentrate the records worth protecting, which is why an access-control slip in one lands hard, as it did when a single Budibase builder could reach another workspace's secrets.
Password-protected shares deserve their own line. The advisory notes the password is checked only when the share token is issued, not when the cached response is read. Once any request has warmed the cache for a protected share's URL, an anonymous visitor or a different share can pull that same payload back without ever seeing the password prompt. The protection you added is bypassed by a cache hit. With Redis-backed caching the poisoned entry also survives a server restart, so a reboot is not a cleanup.
Why your logs will not flag it
This is the uncomfortable part. A cache hit that serves the wrong party looks identical to a legitimate cache hit. There is no failed login, no 403, no permission error to alert on, because the permission check simply never runs on the second request. Nothing in the access log separates the anonymous visitor who received a share's private records from one who received the public homepage. Treating the pre-upgrade window as a possible exposure, rather than waiting for evidence of a leak, is therefore the honest posture: the evidence would not appear in the places you normally look.
If you ran an affected version with caching and shares in production, assume any data exposed through a share during a cache window could have reached the wrong audience for the length of your CACHE_TTL. Shares that carried sensitive records are worth revoking and reissuing after you patch.
A cache in front of authorization is part of the authorization
The wider lesson outlives this one product. Any cache that sits in front of an authorization decision becomes part of that decision. If the cache key does not include every dimension the permission logic depends on, the cache silently answers requests the permission logic would have refused. Web-cache authorization bugs keep recurring for this reason: the caching layer is added for speed by people reasoning about throughput, not access control, so the key ends up describing the request instead of the requester. Directus is not alone here. A single encoded character recently walked a Fastify proxy past the internal routes it meant to hide, and a low-privilege account could overreach on Dell's Data Domain backup appliances. Different products, one root cause: a control that trusted the wrong signal.
Patch first, then rotate the shares that mattered
- Upgrade to Directus 12.0.0. The fix folds the missing authorization context into the cache key, so share, anonymous, and scoped responses no longer collide. This is the real remedy.
- If you cannot upgrade immediately, disable response caching by setting
CACHE_ENABLED=false. You lose the performance benefit, but you close the leak until you can patch. - Revoke and reissue shares that carried sensitive data. A patched server does not undo an exposure that already happened during a cache window. Reissue the links that mattered, the password-protected ones first.
- Confirm your exposure honestly. Check whether caching was on and whether you use shares. If both are true and the instance is public, treat it as a possible data exposure for the length of your cache TTL, not a theoretical one.
There is no public exploit and no report of exploitation in the wild at the time of writing, and the flaw's EPSS probability score sits low. That is the good news, and the reason to move now rather than under fire. The fix is a clean version bump, and the window to close this quietly is still open.
| First request warms the cache | Later request reads the same key | What the second party receives |
|---|---|---|
| A password-protected share | An anonymous visitor | The share's scoped data, with no password ever entered |
| Share A, meant for one recipient | Share B, a different recipient | Share A's filtered records, sent to the wrong party |
| An anonymous request | A share token (or the reverse) | Whichever view landed first, regardless of scope |