GHSA-72r2-7mfr-5xr9: Medium severity pip/nltk vulnerability
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 nltkdata 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/nltkdataevil".startswith("/tmp/nltkdata") → True ✗ commonpath(["/tmp/nltkdataevil", "/tmp/nltkdata"]) → "/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 nltkdata blocked: {self.path!r}") stream = open(self.path, "rb")
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/nltkto a version that resolves this vulnerability.Fixed in 3.10.0
Event History
Frequently Asked Questions
What must an attacker be able to control to exploit this issue?
The attacker must be able to cause nltk.data.load() to receive a file:// URL. The target file must also be readable by the process running NLTK.
What is the practical impact of a successful exploit?
An attacker can read any file that the affected process is permitted to read. The supplied severity vector indicates high confidentiality impact, with no integrity or availability impact.
Does exploitation require user interaction or elevated privileges?
The supplied CVSS vector indicates no user interaction is required and that low privileges are required. It also identifies the attack vector as network-accessible.