GHSA-x5ph-mj9p-rfr8: Path Traversal
Summary Setting nltk.pathsec.ENFORCE = True is documented to sandbox all file access to allowed NLTK data directories and raise PermissionError on unauthorized access. However, StreamBackedCorpusView opens files via builtins.open() directly, bypassing pathsec.validatepath() entirely. An attacker who can influence the fileid argument can read arbitrary local files regardless of the ENFORCE setting.
Details nltk/pathsec.py:274 defines the enforcement point: python def open(file, mode="r", kwargs): validatepath(file, context="pathsec.open") return builtins.open(file, mode=mode, kwargs)
StreamBackedCorpusView.open() in nltk/corpus/reader/util.py bypasses this entirely for string paths:
python line 171 — no validatepath() call self.eofpos = os.stat(self.fileid).stsize
line 208 — calls builtins.open directly self.stream = open(self.fileid, "rb")
Also affected: XMLCorpusView and any corpus reader subclass that passes a raw string fileid to StreamBackedCorpusView.
PoC python pocserver.py — StreamBackedCorpusView pathsec.ENFORCE bypass from flask import Flask, request, jsonify import nltk.pathsec as ps from nltk.corpus.reader.util import StreamBackedCorpusView, readlineblock
Strict mode enabled — expected to sandbox all file access ps.ENFORCE = True
app = Flask(name)
@app.post("/read") def readfile(): fname = request.json.get("file") # fileid is user-controlled, passed directly to StreamBackedCorpusView # pathsec.ENFORCE = True is ignored — builtins.open() called internally view = StreamBackedCorpusView(fname, readlineblock, encoding="utf8") return jsonify({"file": fname, "content": view[0]})
app.run(host="0.0.0.0", port=8000) Trigger: curl -s -X POST http://localhost:8000/read \ -H "Content-Type: application/json" \ -d '{"file": "/etc/passwd"}' Confirmed on latest stable NLTK. No privileges required.
Impact - Type: Arbitrary Local File Read / Security Control Bypass - CWE: CWE-22, CWE-284 - OWASP: A01:2021 – Broken Access Control
Affects web apps, REST APIs, and multi-tenant NLP pipelines where user input influences the fileid passed to NLTK corpus readers. Sensitive targets include /etc/passwd, /proc/self/environ (may contain AWSSECRETACCESSKEY, DATABASEURL, etc.), and application config files.
The core issue is that operators who explicitly set ENFORCE = True to harden production deployments are left with a false security guarantee.
Suggested fix: Replace builtins.open() and os.stat() in the string-path branch with nltk.pathsec.open() and nltk.pathsec.validatepath().
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 - Configuration
Do not rely on path sandboxing via nltk.pathsec.ENFORCE alone for string-path corpus reads; the bypass occurs in StreamBackedCorpusView when it calls builtins.open/os.stat internally for raw string fileids.
NLTK pathsec (nltk.pathsec) ENFORCE = True - Configuration
Fix the string-path branch in StreamBackedCorpusView._open() by replacing direct calls to builtins.open() and os.stat() with nltk.pathsec.open() and adding/ensuring nltk.pathsec.validate_path() (the material notes line 171 has no validate_path() call and line 208 calls builtins.open() directly; pathsec.open/validate_path should be invoked before opening/stat’ing).
NLTK StreamBackedCorpusView (nltk/corpus/reader/util.py) File open/stat path validation = Use nltk.pathsec.open + nltk.pathsec.validate_path
Event History
Frequently Asked Questions
Who is exposed to this issue?
Applications are exposed when they enable nltk.pathsec.ENFORCE and allow an attacker to influence a raw string fileid passed to StreamBackedCorpusView. XMLCorpusView and corpus reader subclasses that pass raw string fileid values to StreamBackedCorpusView are also affected.
What does an attacker need to exploit the bypass?
The attacker needs influence over the fileid argument used by an affected corpus view. They can then cause the view to open arbitrary local files despite pathsec.ENFORCE being set to True.
Does enabling pathsec enforcement prevent this attack?
No. StreamBackedCorpusView performs os.stat on the supplied path and opens it through builtins.open() directly, without calling pathsec.validate_path().
How can I identify potentially affected code?
Review uses of StreamBackedCorpusView, XMLCorpusView, and their subclasses for raw string fileid values that can be derived from untrusted input. In particular, look for application code that treats ENFORCE as the control restricting corpus file access.