Skip to main content
CVE-2026-109956VulnerabilityPublished

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

Affects safetySource GCVE Database

0
Compare

Mentioned vulnerabilities

Authoritative record:CVE-2026-109956· High 8.3

Not known to be exploited · CVSS 8.3 High : upgrade to 3.8.1_3 soon.

Data as of 30 Oct 2026, 07:30 UTC

CVSS 4.0 base score
8.3High
How severe the flaw is on the 0–10 CVSS scale.

Thin evidence

One scorer or none
CVSS scores by source
CNA (VulnCheck)CVSS 4.08.3
CNA (VulnCheck)CVSS 3.17.0
Patch
Available – fixed in 3.8.1_3
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

Exploitation evidence

t = 0 is the CVE’s publication on cve.org

  1. Disclosed: evidence found.

    CVE published 13 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: evidence found.

    CISA SSVC: proof of concept public

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

Timeline

  1. CVE ID reserved

    by VulnCheck

    , 2 hours before disclosure

  2. CVE published

    on cve.org

    , t = 0

  3. CISA SSVC assessment

    proof of concept public

    , 13 days after disclosure

  4. In Threat Radar

    record ingested

    , 13 days after disclosure

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 29 Oct 2026.

What to do

Upgrade safety to 3.8.1_3.

Affected software

ProductAffectedFixed in
Homebrewsafetypkg:brew/safetyOther threatsAffected
  • >=3.3.1 <3.8.1_3
Fixed in
  • 3.8.1_3

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## 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. ## 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", <staged>, ...]` (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 <path>` / `-l <path>` 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 v

References

Community reviews

Loading reviews…