CVE-2026-65915: NLTK: dead sandbox check enables arbitrary file read
GHSA-72r2-7mfr-5xr9 MEDIUM CISA: TRACK*A patched-but-broken guard in NLTK's FileSystemPathPointer.open() means the 'security fix' compares a normalized path against itself, which is always true, so the block never fires — any string reaching nltk.data.load() or nltk.data.find(), including a file:// URL, can pull back the contents of any file the process can read. With 2,997 downstream dependents and NLTK embedded in NLP preprocessing pipelines, notebook servers, and multi-tenant AI services, the blast radius for apps that expose corpus or resource selection to user input is significant. The EPSS score is low in absolute terms (0.36%) but still sits in the top 71st percentile of scored CVEs, there is no CISA KEV listing and no public exploit or Nuclei template yet, so this is not under active mass exploitation — but the bug takes zero skill to trigger, just one file:// string, once an attacker finds an entry point. Patch to NLTK 3.10.0 now, and in the interim audit every code path where user-controlled strings reach nltk.data.load()/find() and hard-block the file:// scheme or allowlist known corpus names.
What is the risk?
CVSS 6.5 (medium) reflects high confidentiality impact with no integrity or availability loss, network attack vector, low complexity, and low privileges required — but real-world risk hinges entirely on whether an application exposes the string passed to nltk.data.load()/find() to untrusted input. Where it does, exploitation is trivial (a crafted file:// URL, no auth bypass tricks, no chaining needed) and reliable across platforms. EPSS at 0.36% (71st percentile) and absence from CISA KEV indicate no evidence of active exploitation yet, and there's no public PoC beyond the disclosure writeup or scanner template, so this is a 'patch on the next cycle, prioritize if you know you're exposed' item rather than a fire drill. The package's broader risk profile (OpenSSF Scorecard 5.7/10, 48 other CVEs in the same package) suggests NLTK warrants ongoing scrutiny beyond this single issue.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Jupyter Notebook | pip | <= 3.9.3 | 3.10.0 |
Do you use Jupyter Notebook? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step-
Upgrade to NLTK >= 3.10.0, which fixes the sandbox check. Where upgrading isn't immediate: audit every call site of nltk.data.load() and nltk.data.find() for user-influenced arguments, and either eliminate that influence or strictly allowlist against a fixed set of known-good corpus/resource names — never pass raw user input, and explicitly reject any value containing 'file://', '/', or '..'. Run the process with least-privilege file permissions so a successful read has minimal value even if the check is bypassed. For detection, monitor for anomalous file reads (especially of /etc/passwd, .env, credential files, or SSH keys) originating from processes that import nltk, and alert on file:// scheme strings appearing in application logs or request parameters tied to NLTK resource loading.
What does CISA's SSVC say?
Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-65915?
A patched-but-broken guard in NLTK's FileSystemPathPointer.open() means the 'security fix' compares a normalized path against itself, which is always true, so the block never fires — any string reaching nltk.data.load() or nltk.data.find(), including a file:// URL, can pull back the contents of any file the process can read. With 2,997 downstream dependents and NLTK embedded in NLP preprocessing pipelines, notebook servers, and multi-tenant AI services, the blast radius for apps that expose corpus or resource selection to user input is significant. The EPSS score is low in absolute terms (0.36%) but still sits in the top 71st percentile of scored CVEs, there is no CISA KEV listing and no public exploit or Nuclei template yet, so this is not under active mass exploitation — but the bug takes zero skill to trigger, just one file:// string, once an attacker finds an entry point. Patch to NLTK 3.10.0 now, and in the interim audit every code path where user-controlled strings reach nltk.data.load()/find() and hard-block the file:// scheme or allowlist known corpus names.
Is CVE-2026-65915 actively exploited?
No confirmed active exploitation of CVE-2026-65915 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-65915?
Upgrade to NLTK >= 3.10.0, which fixes the sandbox check. Where upgrading isn't immediate: audit every call site of nltk.data.load() and nltk.data.find() for user-influenced arguments, and either eliminate that influence or strictly allowlist against a fixed set of known-good corpus/resource names — never pass raw user input, and explicitly reject any value containing 'file://', '/', or '..'. Run the process with least-privilege file permissions so a successful read has minimal value even if the check is bypassed. For detection, monitor for anomalous file reads (especially of /etc/passwd, .env, credential files, or SSH keys) originating from processes that import nltk, and alert on file:// scheme strings appearing in application logs or request parameters tied to NLTK resource loading.
What systems are affected by CVE-2026-65915?
This vulnerability affects the following AI/ML architecture patterns: NLP preprocessing pipelines, notebook servers, multi-tenant AI pipelines, training pipelines.
What is the CVSS score for CVE-2026-65915?
CVE-2026-65915 has a CVSS v3.1 base score of 6.5 (MEDIUM). The EPSS exploitation probability is 0.36%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0037 Data from Local System AML.T0055 Unsecured Credentials Compliance Controls Affected
What are the technical details?
Original Advisory
### Summary There's a logic bug in `FileSystemPathPointer.open()` inside `nltk/data.py` that makes the sandbox check permanently inert. The guard condition is always `False` — meaning any file the process can read is accessible by passing a `file://` URL to `nltk.data.load()`. --- ### Details In `nltk/data.py`, `FileSystemPathPointer.open()` was patched at some point with a comment saying "SECURITY PATCH ENFORCING SANDBOX", but the check doesn't work: ```python def open(self, encoding=None): path = os.path.normpath(self._path) # Block raw absolute reads such as "/" "C:\\Windows" etc. if os.path.isabs(path) and path != os.path.normpath(self._path): raise ValueError(f"Direct absolute file access blocked: {path}") stream = open(self._path, "rb") ``` `path` is set to `os.path.normpath(self._path)` on line 1, then compared against `os.path.normpath(self._path)` again in the condition. They are always equal. The `ValueError` never fires. On top of that, `__init__` already calls `os.path.abspath()` before storing `self._path`, so it's normalized before `open()` is even called. Running `normpath` on it again changes nothing. The `stream = open(self._path, "rb")` line is always reached regardless of what path was passed in. --- ### PoC Tested on Python 3.11, NLTK 3.9.1, Ubuntu 22.04. ```python import nltk from nltk.data import FileSystemPathPointer # direct construction ptr = FileSystemPathPointer("/etc/passwd") with ptr.open() as f: print(f.read(300)) # via load() using file:// URL data = nltk.data.load("file:///etc/passwd", format="raw") print(data[:300]) ``` Both print file contents. No exception is raised. --- ### Impact Any app that lets users influence the string passed to `nltk.data.load()` or `nltk.data.find()` is exposed — web APIs, notebook servers, multi-tenant pipelines. An attacker can read any file the process user has access to: `/etc/passwd`, `.env` files, private keys, `~/.aws/credentials`, etc. ## Suggested Fix **File:** `nltk/data.py` — `FileSystemPathPointer.open()` (lines 378–390) ### What's wrong Line 387 compares `normpath(self._path)` against itself — always equal, so the `ValueError` never fires. The check is dead code. `__init__` already calls `abspath()` on construction, so re-running `normpath` inside `open()` changes nothing either. --- ### Fix Validate against the actual list of permitted data directories instead: ```python def open(self, encoding=None): import nltk.data as _d allowed = [os.path.abspath(p) for p in _d.path if p] if allowed and not any( os.path.commonpath([self._path, r]) == r for r in allowed ): raise ValueError( f"Access outside nltk_data blocked: {self._path!r}" ) stream = open(self._path, "rb") if encoding is not None: stream = SeekableUnicodeStreamReader(stream, encoding) return stream ``` --- ### Why `commonpath` not `startswith` `startswith` is bypassable by a path that shares a prefix: ``` /tmp/nltk_data_evil".startswith("/tmp/nltk_data") → True ✗ commonpath(["/tmp/nltk_data_evil", "/tmp/nltk_data"]) → "/tmp" ✓ ``` --- ### Diff ```diff - path = os.path.normpath(self._path) - if os.path.isabs(path) and path != os.path.normpath(self._path): - raise ValueError(f"Direct absolute file access blocked: {path}") - + import nltk.data as _d + allowed = [os.path.abspath(p) for p in _d.path if p] + if allowed and not any( + os.path.commonpath([self._path, r]) == r for r in allowed + ): + raise ValueError(f"Access outside nltk_data blocked: {self._path!r}") stream = open(self._path, "rb") ```
Exploitation Scenario
A SaaS text-analytics API lets authenticated users select which NLTK corpus or tokenizer resource to use for their document processing job via a request parameter. An attacker, holding only a low-privilege account, submits a job with the resource parameter set to `file:///app/.env` instead of a valid corpus name. The application calls nltk.data.load() with that string; the dead sandbox check never raises, the file opens, and its contents are returned in the job output or error response — handing the attacker the app's database credentials and API keys without ever touching an actual NLTK corpus.
Weaknesses (CWE)
CWE-284 — Improper Access Control: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
- [Architecture and Design, Operation] Very carefully manage the setting, management, and handling of privileges. Explicitly manage trust zones in the software.
- [Architecture and Design] Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N References
- github.com/advisories/GHSA-72r2-7mfr-5xr9
- github.com/nltk/nltk/commit/69db9911fdba914ceeaca7aec6e892d1b14586a9
- github.com/nltk/nltk/pull/3522
- github.com/nltk/nltk/security/advisories/GHSA-72r2-7mfr-5xr9
- github.com/pypa/advisory-database/tree/main/vulns/nltk/PYSEC-2026-3731.yaml
- huntr.com/bounties/a510de7b-ffaf-4a83-9bf8-fa7e63f4bd2d
- nvd.nist.gov/vuln/detail/CVE-2026-65915
- vulncheck.com/advisories/nltk-before-arbitrary-file-read-via-filesystempathpointer
Timeline
Related Vulnerabilities
CVE-2026-72811 10.0 SiYuan: SQL injection enables cross-notebook DB access
Same package: notebook CVE-2026-69083 10.0 SiYuan: unauthenticated SQLi in full-text search endpoint
Same package: notebook CVE-2026-69084 10.0 SiYuan: SQL injection in search endpoint exposes notebooks
Same package: notebook CVE-2026-44727 9.0 jupyter-server: stored XSS yields kernel RCE
Same package: notebook CVE-2026-52798 8.9 Gogs: Stored XSS via .ipynb Markdown re-render bypass
Same package: notebook