Overview · Dispositioned issues
Preview cache and per-key lock map grow without bound; mtime-keyed entries let an authenticated user exhaust disk and memory
low Possibly Valid medium confidence
Status
Possibly Valid
Unbounded disk cache and unpruned in-memory lock map are confirmed in source; gated on the opt-in --cache-dir deployment config, hence possibly_valid.
Repository / Component
Plain-English Description
When on-disk preview caching is turned on, generated thumbnails are never cleaned up and an internal lock table keeps growing. Because the cache key includes a file's modification time, a user can force endless new cache entries by re-uploading, slowly filling the disk and memory.
Description of the Underlying Issue
diskcache.FileCache.Store (diskcache/file_cache.go:34-49) writes cached previews with no size limit, TTL, or eviction, and getScopedLocks (diskcache/file_cache.go:91-103) adds a mutex to an in-memory map for each distinct key and never removes it — even Delete (file_cache.go:66) adds an entry. In the preview miss path (http/preview.go:100-152), each cache miss spawns a detached context.Background() goroutine to Store the result (http/preview.go:145-150) with no backpressure. Critically, previewCacheKey includes f.ModTime.Unix() (http/preview.go:156), so a single file whose mtime changes yields a fresh cache key and thus a new on-disk entry and a new permanent lock-map entry. The cache is a NoOp unless --cache-dir is configured (cmd/root.go:172-179), so the growth only occurs on opt-in cached deployments. This weakness was deduplicated against DOK-100063 (same sink/location); DOK-100063 was not independently published, so this record carries the reportable content.
Potential Attack
On a deployment with --cache-dir configured, an authenticated user with Perm.Download (to trigger previews) plus the ability to change a file's mtime — e.g. Perm.Modify to re-upload/overwrite a file, or introducing many distinct images — repeatedly requests previews. Each mtime change produces a new previewCacheKey, so the same file generates unlimited distinct on-disk cache files and unlimited permanent scopedLocks map entries. No eviction, TTL, or map pruning reclaims them.
Outcomes of Potential Attack
Gradual exhaustion of disk space (unbounded cache files) and process memory (the ever-growing lock map), leading to denial of service for the server and potentially the host once the disk fills or memory is exhausted. The attacker gains no data; the impact is availability and resource abuse.
Affected Scope
diskcache.FileCache.Store / getScopedLocks (diskcache/file_cache.go:34-49, 91-103); previewHandler miss path (http/preview.go:100-152)
Suggested Fix (plain english)
Cap the preview cache with size/count limits and TTL-based eviction, and clean up the internal lock table entries after use.
Suggested Fix (detailed)
Bound the disk cache with a maximum total size and/or entry count using LRU or TTL eviction in diskcache.FileCache.Store (diskcache/file_cache.go:34-49), and prune scopedLocks entries once the associated operation completes so the map (diskcache/file_cache.go:91-103) does not grow monotonically. Consider adding backpressure to the detached Store goroutine at http/preview.go:145-150. Verify by driving repeated previews with changing mtimes under --cache-dir and confirming both the cache directory size and the lock-map size stay bounded. Bound the cache (max size/count with LRU or TTL eviction) and prune scopedLocks entries after use.
Validation
Unbounded disk cache and unpruned in-memory lock map are confirmed in source; gated on the opt-in --cache-dir deployment config, hence possibly_valid.
Full Evidence
diskcache/file_cache.go:34-49 Store has no size limit/TTL/eviction
diskcache/file_cache.go:91-103 scopedLocks map adds a mutex per distinct key and never removes it (even Delete at :66 adds an entry)
http/preview.go:145-150 each cache miss spawns a detached context.Background() goroutine to Store, no backpressure
http/preview.go:156 previewCacheKey includes f.ModTime.Unix(), so a changed mtime yields a fresh key for the same file
cmd/root.go:172-179 cache is a NoOp unless --cache-dir is configured
Proven fact: Both the on-disk preview cache and the in-memory scopedLocks map grow without bound (no eviction; map never pruned)
Proven fact: Because the cache key includes mtime, a single file can be forced to generate unlimited distinct cache entries and lock-map entries by an authenticated user who can change mtimes (re-upload) or introduce many images
Unvalidated fact: Only active when --cache-dir is configured (opt-in); otherwise the cache is a NoOp
Unvalidated fact: The disk/memory ceiling before impact is operator/host dependent
diskcache/file_cache.go:34-49 — Store has no size limit/TTL/eviction
diskcache/file_cache.go:91-103 — scopedLocks map adds a mutex per distinct key and never removes it (even Delete at :66 adds an entry)
http/preview.go:145-150 — each cache miss spawns a detached context.Background() goroutine to Store, with no backpressure
http/preview.go:156 — previewCacheKey includes f.ModTime.Unix(), so a changed mtime yields a fresh key for the same file
cmd/root.go:172-179 — the cache is a NoOp unless --cache-dir is configured
Proven fact: Both the on-disk preview cache and the in-memory scopedLocks map grow without bound (no eviction; map never pruned).
Proven fact: Because the cache key includes mtime, a single file can be forced to generate unlimited distinct cache entries and lock-map entries by an authenticated user who can change mtimes (re-upload) or introduce many images.
Unvalidated fact: Only active when --cache-dir is configured (opt-in); otherwise the cache is a NoOp.
Unvalidated fact: The disk/memory ceiling before impact is operator/host dependent.
diskcache/file_cache.go:34-49 FileCache.Store has no size limit, TTL, or eviction
diskcache/file_cache.go:91-103 scopedLocks map adds a mutex per distinct key and never removes it (unbounded memory)
http/preview.go:145-150 each cache miss spawns a detached goroutine (context.Background()) that writes a new entry, no backpressure
http/preview.go:155-157 cache key includes ModTime.Unix(), so touching mtime yields a fresh key for the same file
cmd/root.go:172-179 cache active only when --cache-dir configured