CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-J456-XH4H-CPF2

CVE-2026-109956: Path Traversal and Sandbox Bypass in NLTK WekaClassifier

Alon Barad
Alon Barad
Software Engineer

Oct 27, 2026·10 min read·5 visits

Executive Summary (TL;DR)

NLTK versions up to 3.10.3 fail to validate model file paths in WekaClassifier.train() and WekaClassifier._classify_many(), bypassing the nltk.pathsec filesystem sandbox and enabling arbitrary file overwrites and local file existence probing via the Weka JVM.

CVE-2026-109956 (GHSA-j456-xh4h-cpf2) is a path traversal vulnerability and security sandbox bypass in the Python Natural Language Toolkit (NLTK) versions 3.10.3 and earlier. The WekaClassifier module fails to validate caller-controlled model file paths before passing them to the Weka Java Virtual Machine (JVM) via command-line arguments. When operating under the nltk.pathsec security sandbox, this omission permits attackers who control model path parameters to read, probe, or overwrite arbitrary files outside authorized NLTK data directories.

Vulnerability Overview

The Natural Language Toolkit (nltk) is a Python library for symbolic and statistical natural language processing. In addition to native Python implementations, NLTK includes wrapper classes that interface with external machine learning tools and Java binaries. One such wrapper is WekaClassifier, located in nltk/classify/weka.py, which bridges NLTK classification workflows with the Waikato Environment for Knowledge Analysis (Weka) toolkit running inside a Java Virtual Machine (JVM).

To restrict filesystem access when processing untrusted inputs or executing within multi-tenant environments, NLTK implements a filesystem security sandbox inside the nltk.pathsec module. When nltk.pathsec.ENFORCE is set to True (the default security posture), NLTK enforces an architectural invariant requiring all caller-controlled file paths, model artifacts, and corpus targets to reside within designated, permitted NLTK data roots (nltk.pathsec._get_allowed_roots()).

In NLTK versions 3.10.3 and earlier, WekaClassifier failed to enforce nltk.pathsec validation on caller-supplied model paths across both training and classification entry points. Specifically, WekaClassifier.train() and WekaClassifier._classify_many() passed unvalidated file path strings directly into the command-line arguments of the Weka JVM subprocess (-d for destination output and -l for model loading).

Classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory), this omission completely bypasses the nltk.pathsec containment mechanism. An attacker capable of supplying or modifying the model filename argument in an application using WekaClassifier can write serialized Java model data to arbitrary filesystem locations or probe the existence and readability of sensitive local files.

Root Cause Analysis

The root cause of CVE-2026-109956 is an incomplete security migration during previous hardening efforts of NLTK's external tool wrappers and model loaders. In earlier security advisories, such as GHSA-8mgp-746c-j5xp (CVE-2026-81726), the maintainers hardened native Python model interfaces including TransitionParser.train, AveragedPerceptron.save, and PerceptronTagger.save_to_json. Sibling native-tool wrappers were also secured: CRFTagger enforced validate_tool_path() on model files, and MaltParser.generate_malt_command() validated its input/output flags (-i, -o) and working directories.

In contrast, nltk/classify/weka.py was only partially integrated with nltk.pathsec. While the module used validate_path() to verify the WEKAHOME environment directory and used pathsec_open() to secure internal temporary ARFF dataset files, the user-supplied model_filename parameter was overlooked in three distinct locations: class initialization (__init__), batch classification (_classify_many), and model training (train).

During model training, WekaClassifier.train() constructs a command array containing ["-d", model_filename, "-t", train_filename] and passes it to nltk.internals.java(). During classification, WekaClassifier.__init__() stores model_filename in self._model without inspection, and _classify_many() subsequently passes ["-l", self._model, "-T", test_filename] to java().

The underlying subprocess execution helper, nltk.internals.java(), performs several security checks before invoking subprocess.Popen(final_cmd, shell=False). It validates classpath JAR containment via _verify_jar_sandbox(), verifies JVM launcher options via _validate_java_options() (mitigating CVE-2026-12841), verifies the main Java class token, and blocks @argfile expansion tokens. However, java() treats all tokens following the main class name as opaque application arguments. Because -d and -l are application-level flags interpreted by weka.classifiers.* inside the JVM rather than by the java launcher itself, nltk.internals.java() passes them directly to the operating system process creation API without validating their path arguments.

Code Analysis

In NLTK 3.10.3 and earlier, nltk/classify/weka.py imported validate_path and pathsec_open, but did not import or invoke validate_tool_path. Below is the vulnerable implementation in nltk/classify/weka.py prior to the fix:

class WekaClassifier(ClassifierI):
    def __init__(self, formatter, model_filename):
        self._formatter = formatter
        # VULNERABLE: model_filename is stored without pathsec validation
        self._model = model_filename
 
    def _classify_many(self, featuresets, options):
        config_weka()
        # ...
        # VULNERABLE: self._model is passed directly to Weka's -l (load) flag
        cmd = [javaclass, "-l", self._model, "-T", test_filename] + options
        java(cmd, classpath=_weka_classpath, stdout= stdout, stderr=stderr)
 
    @classmethod
    def train(
        cls,
        model_filename,
        train_instances,
        classifier="weka.classifiers.bayes.NaiveBayes",
        options=[],
        quiet=True,
    ):
        config_weka()
        # ...
        # VULNERABLE: model_filename is passed directly to Weka's -d (dump/write) flag
        cmd = [javaclass, "-d", model_filename, "-t", train_filename]
        java(cmd, classpath=_weka_classpath)

Commit 6a17606293fe9d3ea1006f99dd3fed2e2b25d519 (PR #3863) remediates the vulnerability by importing validate_tool_path from nltk.pathsec and placing validation guards at all three control points prior to invoking config_weka() or java():

@@ -22,7 +22,7 @@
 from nltk.internals import config_java, java
 from nltk.pathsec import ZipFile as SecureZipFile
 from nltk.pathsec import open as pathsec_open
-from nltk.pathsec import validate_path
+from nltk.pathsec import validate_path, validate_tool_path
 from nltk.probability import DictionaryProbDist
 
 _weka_classpath = None
@@ -107,6 +107,10 @@ def _check_weka_version(jar):
 class WekaClassifier(ClassifierI):
     def __init__(self, formatter, model_filename):
         self._formatter = formatter
+        # Bound the caller-controlled model path to the pathsec data roots before
+        # it reaches the weka JVM as a -l read target, closing the model-artifact
+        # containment gap weka.py was left out of (GHSA-j456-xh4h-cpf2).
+        validate_tool_path(model_filename, context="WekaClassifier", must_exist=False)
         self._model = model_filename
 
     def prob_classify_many(self, featuresets):
@@ -116,6 +120,12 @@ def classify_many(self, featuresets):
         return self._classify_many(featuresets, ["-p", "0"])
 
     def _classify_many(self, featuresets, options):
+        # Re-bound the model path in case _model was reassigned after construction;
+        # weka reads it via -l (GHSA-j456-xh4h-cpf2). Containment, not existence, is
+        # the security property, so must_exist stays False.
+        validate_tool_path(
+            self._model, context="WekaClassifier._classify_many", must_exist=False
+        )
         # Make sure we can find java & weka.
         config_weka()
 
@@ -233,6 +243,15 @@ def train(
         options=[],
         quiet=True,
     ):
+        # Bound the caller-controlled model path before weka writes to it via -d
+        # (GHSA-j456-xh4h-cpf2); it need not exist yet, and validating before any
+        # weka lookup refuses an out-of-root path early.
+        validate_tool_path(
+            model_filename,
+            context="WekaClassifier.train",
+            for_write=True,
+            must_exist=False,
+        )
         # Make sure we can find java & weka.
         config_weka()

The patch implementation addresses potential bypass variants thoroughly:

  1. Early Rejection: Validation occurs before config_weka() is called, ensuring that disallowed paths raise a PermissionError or ValueError immediately without probing for a local Java or Weka installation.
  2. Post-Instantiation Tampering Defense: By re-running validate_tool_path(self._model, ...) inside _classify_many(), the fix prevents a bypass where a caller instantiates WekaClassifier with a benign path and subsequently mutates clf._model before calling classify_many().
  3. Symlink Resolution and Option Smuggling: validate_tool_path canonicalizes paths via os.path.realpath (including parent directory resolution when for_write=True and must_exist=False), blocking symlink escapes placed inside legitimate NLTK data roots. It also rejects NUL bytes (\x00), CLI option-shaped strings (such as -loadModel or --outputFormat), and URL schemes (http://, file:///). Note that a theoretical Time-of-Check to Time-of-Use (TOCTOU) window exists between Python's validate_tool_path() check and the Java process opening the file, though exploiting this requires concurrent local filesystem write access inside an already-trusted NLTK data directory.

Exploitation

Exploiting CVE-2026-109956 requires an application that uses WekaClassifier and exposes the model_filename parameter to untrusted input (for example, an automated ML pipeline, multi-tenant notebook service, or NLP web API allowing users to specify custom model names or paths). For end-to-end execution of the file write or read oracle in the JVM, the host environment must also have Java and the weka.jar archive configured.

The vulnerability provides two primary exploitation primitives:

  1. Arbitrary File Write / Overwrite (-d sink): When WekaClassifier.train(model_filename, train_instances) is called, the Weka JVM serializes the trained Java classifier object (ObjectOutputStream) directly to model_filename. An attacker can provide absolute paths (e.g., /etc/cron.d/evil) or relative traversal sequences (e.g., ../../../home/user/.bashrc). While the attacker only partially controls the file structure (since it is encapsulated in Java serialization headers and model parameter structures), the ability to overwrite arbitrary files owned by the application user allows corrupting critical application state, clobbering configuration files, or injecting payloads into contexts tolerant of binary headers.
  2. File Existence and Format Oracle (-l sink): When WekaClassifier(formatter, model_filename).classify_many(featuresets) is executed, Weka attempts to open and deserialize the target file. Depending on whether the target path does not exist, lacks read permissions, or fails Java stream header parsing (StreamCorruptedException: invalid stream header), Weka outputs distinct error messages to stderr, which NLTK captures and raises in a RuntimeError. This allows an attacker to probe the local filesystem layout.

> [!NOTE] > Furthermore, if an attacker can place a maliciously crafted Java serialized object anywhere on the local filesystem (for example, via a separate temporary file upload mechanism outside the NLTK data roots), they can use the -l path traversal primitive to force the Weka JVM to deserialize that untrusted file via ObjectInputStream.readObject(), potentially escalating to arbitrary code execution via Java deserialization gadgets present in the Weka classpath.

The following proof-of-concept demonstrates how both primitives bypass nltk.pathsec.ENFORCE = True in NLTK <= 3.10.3:

import nltk.pathsec as ps
from nltk.classify.weka import WekaClassifier
 
# Ensure the NLTK filesystem sandbox is active
ps.ENFORCE = True
 
# 1. Arbitrary File Write Escape via WekaClassifier.train (-d flag)
# In <= 3.10.3, validate_tool_path is omitted; Weka writes outside allowed roots.
traversal_write_target = "/tmp/outside_sandbox/evil.model"
sample_data = [({"feature1": 1}, "positive"), ({"feature1": 0}, "negative")]
 
WekaClassifier.train(traversal_write_target, sample_data)
 
# 2. Arbitrary File Read / Deserialization Sink via _classify_many (-l flag)
# Points the Weka JVM to open an unauthorized file outside NLTK data roots.
traversal_read_target = "/etc/passwd"
clf = WekaClassifier(formatter=None, model_filename=traversal_read_target)
clf.classify_many([{"feature1": 1}])

Impact Assessment

The primary security impact of CVE-2026-109956 is a violation of filesystem integrity (VI:H / I:H), accompanied by limited confidentiality (VC:L / C:L) and availability (VA:L / A:L) impacts. Systems that rely on nltk.pathsec.ENFORCE = True to isolate untrusted NLP workflows or third-party model specifications lose containment when WekaClassifier is reachable.

Through the arbitrary file write vector in WekaClassifier.train(), an attacker can overwrite any file writable by the OS user running the Python process. Overwriting application databases, source files, or configuration artifacts results in targeted denial of service or persistent integrity compromise. Through the -l load vector in _classify_many(), attackers can enumerate private files outside the sandbox or trigger Java deserialization of arbitrary local files.

Vulnerability scoring reflects the architectural requirements of the flaw:

  • NVD CVSS v3.1: Scored at 7.0 (High) (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L). The High Attack Complexity (AC:H) reflects the requirement that the target application must explicitly expose WekaClassifier paths to external input and have a working Weka/Java environment configured.
  • VulnCheck CVSS v4.0: Scored at 8.3 (High) (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N), capturing the deployment prerequisite under Attack Requirements Present (AT:P).

According to FIRST.org metrics, the Exploit Prediction Scoring System (EPSS) score for CVE-2026-109956 is 0.00318 (0.32%, 24.59th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

Development and operations teams utilizing NLTK should upgrade to version 3.10.4 or later immediately. Release 3.10.4 incorporates commit 6a17606293fe9d3ea1006f99dd3fed2e2b25d519, which bounds all WekaClassifier model paths using nltk.pathsec.validate_tool_path() and adds regression tests (nltk/test/unit/test_weka_model_path_security.py) covering relative traversal, absolute paths, symlink escapes, NUL byte injection, and CLI option smuggling.

To upgrade via pip:

pip install --upgrade "nltk>=3.10.4"

If an immediate package upgrade is not operationally feasible, apply the following mitigations:

  1. Explicit Caller-Side Validation: Before passing any path to WekaClassifier(formatter, model_filename) or WekaClassifier.train(model_filename, ...), explicitly invoke nltk.pathsec.validate_tool_path(model_filename, context="custom_guard", for_write=True, must_exist=False) in application code.
  2. Disable Weka Integration: If Weka classification is not required by the application, ensure the WEKAHOME environment variable is unset and remove weka.jar from the host environment so config_weka() halts execution before spawning a JVM.
  3. Enforce Sandbox Configuration: Verify that nltk.pathsec.ENFORCE remains set to True (default) and audit configured nltk.data.path directories to ensure they do not contain untrusted symlinks or overly permissive write permissions.

Official Patches

NLTK ProjectGit commit patching nltk/classify/weka.py with validate_tool_path checks.
NLTK ProjectNLTK v3.10.4 release containing the security fix.
NLTK ProjectGitHub Security Advisory GHSA-j456-xh4h-cpf2.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.0/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L
EPSS Probability
0.32%
Top 75% most exploited

Affected Systems

Python Natural Language Toolkit (nltk) <= 3.10.3

Affected Versions Detail

Product
Affected Versions
Fixed Version
nltk
NLTK Project
<= 3.10.33.10.4
AttributeDetail
Vulnerability ClassCWE-22: Path Traversal / Sandbox Bypass
Affected Componentnltk.classify.weka.WekaClassifier (PyPI: nltk <= 3.10.3)
CVSS v3.1 / v4.07.0 High (NVD v3.1) / 8.3 High (VulnCheck v4.0)
Attack VectorNetwork / Caller-Controlled Model Path Argument
EPSS Score0.32% (24.59th Percentile)
Security ImpactArbitrary File Overwrite (-d) and Local File Probing (-l)
Exploit StatusProof-of-Concept (Unit Test Suite Verified)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1005Data from Local System
Collection
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

Known Exploits & Detection

GitHubPull Request #3863 containing unit tests (test_weka_model_path_security.py) and proof-of-concept payloads demonstrating out-of-root writes, reads, and symlink escapes.
GitHub AdvisoryOfficial GitHub Security Advisory detailing the WekaClassifier -d and -l pathsec bypass mechanics.

Vulnerability Timeline

NLTK v3.10.3 released with unhardened model path handling in nltk/classify/weka.py.
2026-08-12
Security researcher zx (Jace / @manus-use) submitted PR #3863 to bound WekaClassifier model paths to pathsec data roots.
2026-09-04
Fix commit 6a17606293fe9d3ea1006f99dd3fed2e2b25d519 merged into the NLTK develop branch.
2026-09-06
PR #3905 merged live probe verification test ghsa_j456_xh4h_cpf2.py.
2026-09-20
CVE-2026-109956 published by CNA VulnCheck.
2026-10-13
CVE-2026-109956 updated in the NVD and CVE List.
2026-10-26
Advisory GHSA-j456-xh4h-cpf2 published alongside fixed release NLTK v3.10.4.
2026-10-27

References & Sources

  • [1]GitHub Security Advisory GHSA-j456-xh4h-cpf2
  • [2]CVE-2026-109956 Record
  • [3]NVD - CVE-2026-109956 Detail
  • [4]NLTK Pull Request #3863: Bound the WekaClassifier model path to the pathsec data roots
  • [5]GitHub Fix Commit 6a17606293fe9d3ea1006f99dd3fed2e2b25d519
  • [6]NLTK Release v3.10.4
  • [7]VulnCheck Advisory: NLTK through 3.10.3 Path Traversal via WekaClassifier Model Path
  • [8]Red Hat Security Advisory for CVE-2026-109956
  • [9]Red Hat Bugzilla #2550515 - CVE-2026-109956
Related Vulnerabilities
CVE-2026-81726CVE-2026-12841

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•30 minutes ago•CVE-2026-114620
5.4

CVE-2026-114620: Server-Side Request Forgery in Authlib RFC 9101 JWT-Secured Authorization Request

Authlib versions prior to 1.9.0 contain a Server-Side Request Forgery (SSRF) vulnerability in the RFC 9101 JWT-Secured Authorization Request (JAR) server implementation. The JWTAuthenticationRequest class passes an unauthenticated, client-controlled request_uri parameter directly to the abstract get_request_object() method before applying any destination validation or client registration policies. Because the official documentation previously recommended implementing this hook as an unrestricted HTTP fetch, deployments following those guidelines allow remote attackers possessing a valid public client_id to trigger outbound network requests to internal services and infer fetch outcomes via error response variations.

Amit Schendel
Amit Schendel
1 views•11 min read
•about 3 hours ago•CVE-2026-35002
9.8

CVE-2026-35002: Eval Injection and Arbitrary Code Execution in Agno Agent Platform

CVE-2026-35002 is an arbitrary code execution vulnerability in the Agno agent platform (formerly Phidata) affecting all versions prior to 2.3.24. The flaw resides in the model execution logic (libs/agno/agno/models/base.py) and tool serialization logic (libs/agno/agno/tools/function.py), where the application directly passes the field_type string parameter into Python's built-in eval() function to resolve string type names into Python data types. An attacker who can influence FunctionCall arguments via prompt injection or supply untrusted dictionary structures for deserialization can execute arbitrary Python expressions within the context of the host process.

Amit Schendel
Amit Schendel
3 views•9 min read
•3 days ago•CVE-2026-115895
8.8

CVE-2026-115895: Code Injection via Unsanitized Import Alias in Happy DOM ECMAScript Module Compiler

CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.

Amit Schendel
Amit Schendel
11 views•10 min read
•3 days ago•CVE-2026-115883
6.0

CVE-2026-115883: Cross-Namespace Access via Dot-Delimited Labels in LangGraph Postgres and SQLite Stores

An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.

Amit Schendel
Amit Schendel
9 views•9 min read
•3 days ago•CVE-2026-115886
6.5

CVE-2026-115886: Unauthenticated Server-Side Request Forgery and Unsafe Reflection in MLflow Scorer Evaluation Endpoints

CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.

Alon Barad
Alon Barad
13 views•10 min read
•3 days ago•CVE-2026-113872
7.5

CVE-2026-113872: Unauthenticated Process Crash in Express Multer via Late Busboy Stream Resumption

CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.

Amit Schendel
Amit Schendel
11 views•12 min read