CVE-2026-65915: NLTK before 3.10.0 Arbitrary File Read via FileSystemPathPointer

Published Aug 22, 2026
·
Updated

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")

Other sources

NLTK versions before 3.10.0 contain a logic bug in FileSystemPathPointer.open() where the sandbox validation check compares a normalized path against itself, making the security check permanently inert. Attackers can pass file:// URLs to nltk.data.load() to read arbitrary files accessible to the process user, including credentials and configuration files.

— MITRE

Affected Software

3 affected componentsFixes available
nltk nltk<3.10.0
nltk nltk<3.10.0
pip/nltk<=3.9.3
3.10.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/nltk to a version that resolves this vulnerability.

    Fixed in 3.10.0
  2. Upgrade

    Upgrade nltk/data.py — FileSystemPathPointer.open() to a version that resolves this vulnerability.

    Fixed in 3.10.0
  3. Configuration

    In nltk/data.py (FileSystemPathPointer.open), replace the permanently-inert validation logic with an allowlist check against the real configured nltk data directories (compute allowed = [os.path.abspath(p) for p in _d.path if p], then require os.path.commonpath([self._path, r]) == r for r in allowed; if the check fails, raise ValueError like "Access outside nltk_data blocked: ...").

    NLTK nltk.data.FileSystemPathPointer.open() Sandbox path validation = Validate against the actual permitted data directories using os.path.commonpath(...)

Event History

Aug 22, 2026
CVE Published
via MITRE·02:12 PM
Data Sourced
via MITRE·02:12 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·03:16 PM
DescriptionSeverityWeaknessAffected Software
Sep 8, 2026
Advisory Published
via GitHub·03:27 PM
Data Sourced
via GitHub·03:27 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed to this issue?

Deployments using NLTK versions before 3.10.0 are exposed when untrusted input can influence values passed to nltk.data.load(). The impact is limited to files readable by the account running the affected process.

2

What does an attacker need to exploit the flaw?

An attacker needs the ability to supply a file:// URL to nltk.data.load(). No user interaction is required, but the attacker must be able to influence that load target through the application.

3

Are confidentiality impacts limited to NLTK data files?

No. A successful exploit can read arbitrary files accessible to the process user, including credential and configuration files; the issue does not provide integrity or availability impact in the supplied assessment.

4

What should be done if upgrading is not immediately possible?

Do not allow untrusted input to reach nltk.data.load(), particularly file:// URLs. Run affected processes with least-privilege filesystem access so sensitive files are not readable by the process account.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203