Dev.to Security 🔐 Cybersecurity 👁 0 📖 4 min read

CVE-2026-126295: CVE-2026-126295: Path Traversal and Unconfined File Access in NLTK BllipParser

CVE-2026-126295: Path Traversal and Unconfined File Access in NLTK BllipParser Vulnerability ID: CVE-2026-126295 CVSS Score: 7.1 Published: 2026-11-13 CVE-2026-126295 (also tracked as GHSA-fg9w-wfp5-j933) is a path

CVE-2026-126295: Path Traversal and Unconfined File Access in NLTK BllipParser

Vulnerability ID: CVE-2026-126295
CVSS Score: 7.1
Published: 2026-11-13

CVE-2026-126295 (also tracked as GHSA-fg9w-wfp5-j933) is a path confinement bypass vulnerability in the Natural Language Toolkit (NLTK) affecting versions 3.10.0 prior to 3.10.4. The flaw resides in nltk/parse/bllip.py within the BllipParser class and its factory method BllipParser.from_unified_model_dir(). Unlike sibling tool wrappers in NLTK that enforce data-root confinement via the nltk.pathsec module, BllipParser passes caller-supplied filesystem paths directly into the third-party bllipparser native C extension without validation. When an application passes untrusted input as model paths or directories to BllipParser, an attacker can force the native extension to open arbitrary files on the local filesystem. This leads to information disclosure through parser diagnostic oracles, local model tampering via shared directories, and denial of service by hanging worker threads on blocking special files (such as FIFOs or device nodes) or exhausting memory via oversized files.

TL;DR

NLTK versions 3.10.0 through 3.10.3 fail to validate model paths in BllipParser against the nltk.pathsec security subsystem. Passing untrusted paths to BllipParser or BllipParser.from_unified_model_dir() allows arbitrary file reads, error-based information disclosure, and denial of service via the native bllipparser C extension. Upgrade to NLTK 3.10.4 to resolve this issue.

⚠️ Exploit Status: POC

Technical Details

  • CVE / Advisory ID: CVE-2026-126295 / GHSA-fg9w-wfp5-j933
  • Primary CWE: CWE-22 (Path Traversal) / CWE-73 / CWE-400
  • Affected Component: nltk.parse.bllip.BllipParser (NLTK >= 3.10.0, < 3.10.4)
  • Attack Vector: Local / Application-level untrusted path input
  • CVSS Score: 7.1 High (CVSS v3.1) / 7.0 High (CVSS v4.0)
  • EPSS Score: 0.20% (9.56th percentile)
  • Impact: Arbitrary file read, error oracle disclosure, and Denial of Service (FIFO hang / OOM)
  • CISA KEV Status: Not Listed

Affected Systems

  • NLTK (Natural Language Toolkit) Python Library (PyPI: nltk)
  • nltk: >= 3.10.0, < 3.10.4 (Fixed in: 3.10.4)

Code Analysis

Commit: fdde6d9

Bound BllipParser model paths to the pathsec data roots

diff --git a/nltk/parse/bllip.py b/nltk/parse/bllip.py
index 4aece8f34c..a26ac72cf3 100644
--- a/nltk/parse/bllip.py
+++ b/nltk/parse/bllip.py
@@ -7,6 +7,11 @@
 # For license information, see LICENSE.TXT

 from nltk.parse.api import ParserI
+from nltk.pathsec import (
+    MAX_TOOL_MODEL_BYTES,
+    validate_tool_dir,
+    validate_tool_path,
+)
 from nltk.termsec import safe_print
 from nltk.tree import Tree

@@ -160,6 +165,37 @@ def __init__(
         parser_options = parser_options or {}
         reranker_options = reranker_options or {}

+        # These paths are caller-supplied and the bllipparser C extension opens
+        # and reads them natively, so they are bounded to the NLTK data roots
+        # first, exactly as every sibling tool wrapper does (GHSA-fg9w-wfp5-j933,
+        # CWE-22/73/400). pathsec.open cannot be used because the extension does
+        # its own open, so the guards validate the path and RETURN the checked
+        # string, and the extension is called on that string: a PathLike may
+        # answer differently on a later __fspath__, so re-reading it would let a
+        # swapped value be opened. parser_model names a model DIRECTORY the loader
+        # reads from; the reranker arguments are files it reads whole, so a
+        # require_private model keeps another local user from planting it and
+        # max_bytes refuses a memory-exhausting model.
+        if parser_model is not None:
+            parser_model = validate_tool_dir(
+                parser_model,
+                context="BllipParser parser model",
+                require_private=True,
+            )
+        if reranker_features and reranker_weights:
+            reranker_features = validate_tool_path(
+                reranker_features,
+                context="BllipParser reranker features",
+                max_bytes=MAX_TOOL_MODEL_BYTES,
+                require_private=True,
+            )
+            reranker_weights = validate_tool_path(
+                reranker_weights,
+                context="BllipParser reranker weights",
+                max_bytes=MAX_TOOL_MODEL_BYTES,
+                require_private=True,
+            )
+
         self.rrp = RerankingParser()
         self.rrp.load_parser_model(parser_model, **parser_options)
         if reranker_features and reranker_weights:
@@ -241,6 +277,16 @@ def from_unified_model_dir(
         :type reranker_options: dict(str)
         :rtype: BllipParser
         """
+        # get_unified_model_parameters() belongs to the third-party bllipparser
+        # package and joins sub-paths under the caller's model_dir with no data
+        # root check, so model_dir is bounded here before those sub-paths are
+        # derived (GHSA-fg9w-wfp5-j933). The constructor re-validates each derived
+        # path, so this is defense in depth with a clear up-front error.
+        model_dir = validate_tool_dir(
+            model_dir,
+            context="BllipParser unified model directory",
+            require_private=True,
+        )
         (
             parser_model_dir,
             reranker_features_filename,

Mitigation Strategies

  • Upgrade the NLTK library to version 3.10.4 or later to enforce centralized pathsec data-root confinement in BllipParser.
  • Prohibit passing caller-controlled or unvalidated filesystem paths directly to BllipParser() or BllipParser.from_unified_model_dir().
  • Store all BLLIP parser and reranker models within private, properly permissioned nltk_data directories rather than shared or world-writable paths such as /tmp.
  • Use a strict allowlist mapping logical model names to fixed internal paths when applications allow users to select parsing models.

Remediation Steps:

  1. Audit project dependencies and virtual environments to identify installations of nltk between versions 3.10.0 and 3.10.3 (pip show nltk).
  2. Scan application source code for direct calls to BllipParser or from_unified_model_dir that accept dynamic variables (grep -rnE "BllipParser\(|from_unified_model_dir\(" /path/to/project/).
  3. Upgrade nltk to version 3.10.4 or higher using pip install --upgrade "nltk>=3.10.4".
  4. Verify that any custom model directories used with BllipParser are registered in nltk.data.path and have restrictive filesystem permissions (non-world-writable) so they pass require_private=True checks.

References

Read the full report for CVE-2026-126295 on our website for more details including interactive diagrams and full exploit analysis.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.