Summary
NLTK: pathsec SSRF protection can be bypassed when a proxy is configured
Current NLTK source reopens SSRF in proxied environments. pathsec.urlopen() validates the requested hostname locally, but once proxy inheritance is enabled the real fetch is performed by the proxy rather than by the validated direct-connect socket path.
Details
- Vulnerability type: Server-side request forgery
- Affected component:
nltk.pathsec.urlopen,nltk.data.load,nltk.downloader.Downloader.index,nltk.downloader.Downloader.download - Affected versions: Current source
v3.10.0-rc2; published3.9.4was a negative control and did not reproduce. - Patched versions: Not yet patched
- Root cause: Proxy-handler inheritance disables
_SafeHTTPHandlerand_SafeHTTPSHandler, so the validated hostname no longer matches the actual egress destination.
The hardened direct path pins the validated numeric destination IP before opening the socket. The proxied branch instead copies ProxyHandler instances from the global opener, marks the request as proxied, and skips the pinned handlers. I confirmed that a validated public URL can be fetched from a loopback-only internal service through the proxy path via pathsec.urlopen(), nltk.data.load(), Downloader.index(), and Downloader.download().
PoC
Preconditions
- The runtime has an HTTP proxy configured and the caller relies on
pathsecto keep network fetches SSRF-safe.
Steps
- Start a loopback-only HTTP server that serves secret text, a valid downloader index, and a ZIP payload.
- Configure a proxy that forwards a validated public URL to that internal loopback service.
- Call
pathsec.urlopen()ornltk.data.load()on the public URL and observe the internal response is returned. - Instantiate
Downloader(server_index_url=...), callindex()anddownload(), and observe internal-only content is parsed and installed.
Minimal reproducible excerpt
{'urlopen': 'PROXY_TEXT_SECRET', 'data_load': 'PROXY_TEXT_SECRET', 'downloaded_file': 'INTERNAL_ZIP_SECRET'}
References
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/pathsec.py#L468-L518
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/data.py#L1247-L1283
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/downloader.py#L875-L889
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/downloader.py#L1220-L1226
- https://github.com/nltk/nltk/blob/3.9.4/nltk/pathsec.py#L245-L250
Fix + attack demonstration (verified)
NLTK cannot pin the egress through a proxy, so it stops pretending to: under ENFORCE a proxied fetch is refused rather than performed unvalidated. Operators who trust their proxy opt back in with NLTK_ALLOW_PROXIED_URLOPEN=1 or nltk.pathsec.ALLOW_PROXIED_FETCH=True; under ENFORCE=False the refusal degrades to a warning. This closes the whole class (environment proxies and explicit ProxyHandler alike), because NLTK declines any fetch whose egress it cannot validate.
Attack demonstration (reproduced; captured output)
A loopback HTTP server stands in for the internal target; http_proxy points at it; NLTK is asked for a public IP URL.
Before the fix, the internal secret is exfiltrated through the proxy:
validate_network_url(public): PASSED
*** BYPASS: pathsec.urlopen returned INTERNAL content via proxy: 'INTERNAL_ONLY_SECRET'
After the fix, five scenarios, isolated subprocesses:
| Scenario | Result |
|---|---|
| proxied (env) + ENFORCE | PermissionError, blocked |
| proxied + opt-in | returns secret, escape hatch works |
explicit ProxyHandler (not env) + ENFORCE |
PermissionError, blocked (whole class) |
| no proxy (direct) | internal IP still refused, pinning intact |
proxied + ENFORCE=False |
returns secret + warns |
Tests
nltk/test/unit/test_pathsec.py: 64 passed. Added an end-to-end regression (test_proxied_fetch_does_not_reach_internal_target) plus test_env_proxy_fails_closed_under_enforce; the prior test_env_proxy_skips_pinning_handlers (which encoded the vulnerable path) is re-expressed as the opt-in case. Existing direct-path DNS-rebinding and IP-policy tests unchanged and passing. pre-commit (isort/black/ruff) clean.
Note
The upfront validate_network_url() and the direct-path IP pinning (from the earlier DNS-rebinding fixes, CVE-2026-54296 / GHSA-qvv7) are unchanged, this only closes the proxied downgrade they didn't cover.
Impact
Consumers that trust pathsec as an SSRF barrier in proxied environments can be made to read internal-only HTTP resources, load forged downloader indexes, and install attacker-chosen package content fetched from the proxy's network view.
Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Preserve destination validation for the actual proxy egress target or fail closed when the request would otherwise downgrade into an unpinned proxied path. Add regression tests across pathsec.urlopen, nltk.data.load, and downloader fetches with a configured proxy.
Frequently Asked Questions
- What is CVE-2026-78682? CVE-2026-78682 is a high-severity server-side request forgery (SSRF) vulnerability in nltk (pip), affecting versions <= 3.10.2. It is fixed in 3.10.3. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.
- Which versions of nltk are affected by CVE-2026-78682? nltk (pip) versions <= 3.10.2 is affected.
- Is there a fix for CVE-2026-78682? Yes. CVE-2026-78682 is fixed in 3.10.3. Upgrade to this version or later.
- Is CVE-2026-78682 exploitable, and should I be worried? Whether CVE-2026-78682 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-78682 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-78682? Upgrade
nltkto 3.10.3 or later.