GitHub Advisoryadvisory · PyPI
GHSA-72r2-7mfr-5xr9 NLTK: FileSystemPathPointer.open() sandbox check is dead code — arbitrary file read via file:// protocol
MEDIUM
Official page ↗
- Aliases
- CVE-2026-65915
- Ecosystem
- PyPI
- Packages
- nltk
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
- Weaknesses
- CWE-284
- Reviewed
- 2026-09-08
Affected packages
• PyPI nltk: < 3.10.0 → fixed in 3.10.0
Description
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")
```
Related
Alias · CVE (1)
- CVE-2026-65915 NLTK versions before 3.10.0 contain a logic bug in FileSystemPathPointer.open() where the sandbox validation check compares a normalized… vulnerability
Weakness · CWE (1)
- CWE-284 Improper Access Control weakness
References
- https://github.com/nltk/nltk/security/advisories/GHSA-72r2-7mfr-5xr9 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-65915 advisory
- https://github.com/nltk/nltk/pull/3522 web
- https://github.com/nltk/nltk/commit/69db9911fdba914ceeaca7aec6e892d1b14586a9 web
- https://github.com/nltk/nltk package
- https://github.com/pypa/advisory-database/tree/main/vulns/nltk/PYSEC-2026-3731.yaml web
- https://huntr.com/bounties/a510de7b-ffaf-4a83-9bf8-fa7e63f4bd2d web
- https://www.vulncheck.com/advisories/nltk-before-arbitrary-file-read-via-filesystempathpointer web