KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 16:01 UTC

Back to the wire
Permalink
VulnerabilityGHSA-j456-xh4h-cpf2

NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment

This is a standalone, unfixed variant of the same class as CVE-2026-81726, on a code path the CVE-2026-81726 fix never touched. That advisory enumerates 6 functions (`TransitionParser.train`, `TransitionParser.parse`, `AveragedPerceptron.save`, `AveragedPerceptron.load`, `PerceptronTagger.

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: nltk: 3.10.4. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-j456-xh4h-cpf2 (opens a new tab)

What happened

## Summary `WekaClassifier` in NLTK passes a caller-controlled model file path to the weka Java VM without any `nltk.pathsec` validation. `WekaClassifier.train` puts the caller's `model_filename` verbatim into the weka argv `["-d", model_filename, ...]` (weka *writes* the trained model there), and `WekaClassifier._classify_many` puts `self._model` into `["-l", self._model, ...]` (weka *reads* it). Under `nltk.

pathsec.ENFORCE=True` (the default on current HEAD), NLTK's stated invariant — established by advisory CVE-2026-81726 / GHSA-8mgp-746c-j5xp — is that caller-controlled model-artifact paths must be bounded to the allowed NLTK data roots before any native tool opens them.

`WekaClassifier` is the one `java()`-based tool wrapper that skips this entirely, giving an untrusted workflow arbitrary-location file **write** (`-d`) and an arbitrary file **read/parse oracle** (`-l`), defeating pathsec containment.

This is a standalone, unfixed variant of the same class as CVE-2026-81726, on a code path the CVE-2026-81726 fix never touched. That advisory enumerates 6 functions (`TransitionParser.train`, `TransitionParser.parse`, `AveragedPerceptron.save`, `AveragedPerceptron.load`, `PerceptronTagger.save_to_json`, `save_maxent_params`) — `WekaClassifier` is not among them, and weka.py contains no `validate_tool_path`/`validate_model_resource` at all.

Continue reading

## Root Cause `WekaClassifier.train(cls, model_filename, ...)` builds `cmd = [javaclass, "-d", model_filename, "-t", train_filename]` (nltk/classify/weka.py:256) and `WekaClassifier._classify_many` builds `[..., "-l", self._model, "-T", , ...]` (weka.py:131-133); `self._model` is stored unvalidated in `__init__` (weka.py:108-110). Only the internally-generated `train.arff`/`test.

arff` are staged via `make_staging_dir`; the model path is passed as-is. `nltk.internals.java()` (internals.

py:388-431) validates the classpath (`_verify_jar_sandbox`), the JVM launcher options (`_validate_java_options`, the CVE-2026-12841 fix), the main-class token `cmd[0]`, and rejects `@argfile` tokens in any position — but it explicitly permits program arguments starting with `-` and never inspects the interior of a program-arg path value. So `-d ` / `-l ` flow unchecked to `subprocess.

Popen(final_cmd, shell=False)` and the weka JVM performs an unbounded native open/write on the caller-chosen path.

Sibling wrappers prove the intended pattern: `CRFTagger.train`/`set_model_file` call `validate_tool_path` (crf.py:134,300); `MaltParser.generate_malt_command` validates `-i`/`-o` via `validate_tool_path` (malt.py:340,344), its model via `validate_model_resource` (malt.py:326) and `working_dir` via `validate_path` (malt.py:206). weka.py already uses pathsec elsewhere (`validate_path` for WEKAHOME, `pathsec_open` for ARFF writes) but never for the model path.

## Impact Under `pathsec.ENFORCE=True` (default on HEAD), an untrusted workflow that chooses the weka model save/load path obtains: - Arbitrary-location file **write** via `-d` — overwrite or create a file at an attacker-chosen absolute or `..`-traversal path outside the allowed NLTK data roots (content constrained to weka's serialized-model format; the target choice is the integrity impact).

This corrupts/clobbers arbitrary chosen files. - Arbitrary file **read/parse oracle** via `-l` — weka loads an out-of-root path as a model, yielding an existence/parse differential (file content is not echoed back → confidentiality impact is limited).

This defeats the pathsec containment advisory CVE-2026-81726 promises, at the same privilege level / threat model that advisory was accepted under.

## Proof of Concept ```python import nltk.pathsec as ps; ps.ENFORCE = True from nltk.classify.weka import WekaClassifier # write-side: model written OUTSIDE any allowed NLTK data root (refused for CRFTagger, allowed here) WekaClassifier.train("/tmp/outside_sandbox/evil.model", [({"a": 1}, "pos"), ({"a": 0}, "neg")]) # read-side: -l opens an arbitrary path clf = WekaClassifier(fmt, "/home/victim/secret.model") clf.

classify_many([{"a": 1}]) ``` The argv construction — the vulnerable step — is confirmed without weka installed (see Bypass Evidence); a configured weka.jar is needed only to observe the JVM's actual file I/O.

## Attack Chain 1. **Entry.** Action: an app running with `pathsec.ENFORCE=True` lets an untrusted workflow choose the weka model path and calls `WekaClassifier.train("../../../home/victim/x", featuresets)` (write) or `WekaClassifier(fmt, "/home/victim/secret.model").classify_many(...)` (read). Guard: none — plain classmethod/constructor parameter (weka.py:230,108-110). Bypass proof: grep shows weka.

py uses no `validate_tool_path`/`validate_model_resource`/`validate_tool_dir` (NONE). 2. **Command build.** Action: the path is placed into `cmd = [javaclass, "-d", model_filename, "-t", train_filename]` (weka.py:256) / `["-l", self._model, ...]`. Guard: only the internally-generated `train.arff` is staged via `make_staging_dir`; the model path is passed as-is. Bypass proof: runtime capture yielded `['weka.

classifiers.bayes.NaiveBayes', '-d', '/tmp/outside_sandbox_weka.model', '-t', ' /train.arff']` — the raw out-of-root path is `cmd[2]`. 3. **java() sink.** Action: `java(cmd, classpath=_weka_classpath, ...)` → `subprocess.Popen(final_cmd)` (internals.py:431). Guard: `java()` validates classpath, JVM `options`, `cmd[0]`, and rejects `@argfile` tokens.

Bypass proof: none of these inspect the `-d`/`-l` program-arg value; internals.py explicitly documents that program args after the main class may start with `-`, and the path is neither `cmd[0]` nor an `@`-token, so every `java()` gate passes. Negative control: the same path through `validate_tool_path(..., for_write=True, must_exist=False)` raises `PermissionError`. 4. **Impact.

** Action: the weka JVM opens the path outside the allowed roots → arbitrary-location write (`-d`) / parse-existence oracle (`-l`). Guard: pathsec is never consulted (no `validate_tool_path`, no `pathsec_o

Affected package ranges reported by GitHub: nltk: <= 3.10.3

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-j456-xh4h-cpf2 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-j456-xh4h-cpf2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 27). NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment. https://kevwire.com/record/GHSA-j456-xh4h-cpf2

MLA

"NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment." KEVWire, 27 Oct. 2026, kevwire.com/record/GHSA-j456-xh4h-cpf2.

Chicago

KEVWire. "NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment." Last modified October 27, 2026. https://kevwire.com/record/GHSA-j456-xh4h-cpf2.

Evidence summary (plain text)

NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment (Oct 27, 2026).
This is a standalone, unfixed variant of the same class as CVE-2026-81726, on a code path the CVE-2026-81726 fix never touched. That advisory enumerates 6 functions (`TransitionParser.train`, `TransitionParser.parse`, `AveragedPerceptron.save`, `AveragedPerceptron.load`, `PerceptronTagger.save_to_json`, `save_maxent_params`) — `WekaClassifier` is not among them, and weka.py contains no `validate_tool_path`/`validate_model_resource` at all.

Sibling wrappers prove the intended pattern: `CRFTagger.train`/`set_model_file` call `validate_tool_path` (crf.py:134,300); `MaltParser.generate_malt_command` validates `-i`/`-o` via `validate_tool_path` (malt.py:340,344), its model via `validate_model_resource` (malt.py:326) and `working_dir` via `validate_path` (malt.py:206). weka.py already uses pathsec elsewhere (`validate_path` for WEKAHOME, `pathsec_open` for ARFF writes) but never for the model path.
Sources:
GitHub-reviewed advisory GHSA-j456-xh4h-cpf2: https://github.com/advisories/GHSA-j456-xh4h-cpf2
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-j456-xh4h-cpf2.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 27, 2026, 16:00 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail