Hall of Fame
Back to Hall of Fame Contents Back to WeKan Website

CacheBleed

CVEIconVulnerability nameDate Responsible Security Disclosure byStarsProcessVulnerabilities
-

CacheBleed 2026-10-02 alham-rizvi Gold starGold starGold starGold starGold star Reported privately by alham-rizvi through GitHub Security Advisory GHSA-w3qg-pf27-g68r, with details, a proof of concept and a suggested fix; validated and fixed by xet7.

Attachments were answered with Cache-Control: public, max-age=31536000 and no Vary, after the board access check. A shared cache in front of WeKan (a CDN, a caching reverse proxy, a corporate proxy) could store a board member's copy and serve it to anyone who knew the URL, without credentials, for a year, also after the member was removed from the board.

Validation found the same header in both download branches and both avatar routes, and Meteor-Files' own /cdn/storage route sent its default public, one-year header as well.

Every file response, its 304 and the thumbnail are now private, no-cache with Vary: Cookie, Authorization, X-Auth-Token, on public boards too, because a board made private later would otherwise leave a public copy behind.

CWE-524, CWE-525. Medium severity (CVSS 5.9). No CVE assigned. Affected v8.16 through v12.14. Fix prepared locally for the Upcoming release; not yet released.

Details

Advisory GHSA-w3qg-pf27-g68r reported that WeKan authorized an attachment download against the board and then marked the response public for a year. public explicitly allows a shared cache to store a response produced for an authenticated request, and with no Vary on the credential the cache key is only the URL. A later request for that URL, with no cookie and no token, is answered from the cache without reaching WeKan. The report showed this with WeKan's own header function and an RFC 9111 cache implementation, and gave the nginx setup for an end-to-end check.

Reading the source confirmed the three places the report named and found three more: both avatar routes, and Meteor-Files' own /cdn/storage/attachments/<id>/original/<name> route, which is guarded by WeKan's access callback but sent the library's default public, max-age=31536000, s-maxage=31536000. The header came in with the universal file server in v8.16. Attachment IDs are random, so the URL has to be known: a user later removed from the board, proxy or access logs, browser history, chat or Referer data.

Fix

One helper sets the policy for every response that carries a stored file: Cache-Control: private, no-cache keeps it out of every shared cache and makes the browser revalidate before each use, so the access check runs again and a revocation takes effect at once. Vary: Cookie, Authorization, X-Auth-Token names the credentials for any cache that ignores private. The 304 for a revalidation and the thumbnail carry the same headers, and both file collections pass the same value to Meteor-Files. The suggested fix kept a public, one-year cache for public boards; the fix does not, because a board can be made private later and a copy cached as public would outlive that change. The ETag keeps revalidation a bodiless 304.

Detection

None. The fix changes the headers of responses that were authorized, and nothing is refused. The attack happens in a shared cache that WeKan never sees, so there is no attempt to record in Admin Panel → Problems.

Verification

A unit test drives the policy against the shared-cache rules the report used: the old headers are stored and reused for an anonymous request, the new ones are neither. It checks each file route, the 304 and the thumbnail. A tree-wide negative test fails on any public Cache-Control value, any file collection left on the Meteor-Files default, and any file route that sends an ETag without the policy. A Playwright test checks the headers on the original, the download, the thumbnail and Meteor-Files' route, and that a signed-out request for a private board's file is refused. The browser test and an end-to-end run behind a caching proxy were not run where the fix was written.

Source fix and regression tests

Follow-up fixes (2026-10-02)

API answers now carry Cache-Control: no-store unless a route sets its own, and the legacy attachments route answers only GET and HEAD, with the private cache headers. Fix.