CVE-2026-109956
NLTK through 3.10.3 Path Traversal via WekaClassifier Model Path
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- High
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
8.3
NLTK through 3.10.3 Path Traversal via WekaClassifier Model Path
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
CVE-2026-109956- CVSS 4.0
- 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 - CVSS 3.1
- 7.0 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-98660
8.3
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
EUVD-2026-98660- CVSS 4.0
- 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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9fgx-q9j8-3j83
8.3
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows...
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
GHSA-9fgx-q9j8-3j83- CVSS 4.0
- 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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.0 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L - Weakness
- CWE-22
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-j456-xh4h-cpf2
8.6
NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment
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
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")])
#…
- Record
GHSA-j456-xh4h-cpf2- CVSS 3.1
- 8.6 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L - Weakness
- CWE-22, CWE-73
- Updated
- 2026-11-02
NVD
8.3
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
CVE-2026-109956- CVSS 4.0
- 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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.0 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
8.3
NLTK through 3.10.3 Path Traversal via WekaClassifier Model Path
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
CVE-2026-109956- CVSS 4.0
- 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 - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-109956
8.3
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
DEBIAN-CVE-2026-109956- CVSS 4.0
- 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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Updated
- 2026-11-02
OSVGHSA-j456-xh4h-cpf2
8.6
NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment
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
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")])
#…
- Record
GHSA-j456-xh4h-cpf2- CVSS 3.1
- 8.6 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L - Weakness
- CWE-22, CWE-73
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-109956
8.3
NLTK through 3.10.3 contains a path traversal vulnerability in WekaClassifier that allows attackers to bypass nltk.pathsec containment by supplying unvalidated model paths. Attackers controlling the model_filename or model argument can make the weka JVM write files via -d or probe files via -l outside allowed data roots.
- Record
UBUNTU-CVE-2026-109956- CVSS 4.0
- 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 - CVSS 3.1
- 7.0 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
nltkPyPI |
through 3.10.3 | 3.10.4 |
nltkDebian:12 |
All versions, plus listed versions 3.10.0-13.10.1-13.10.3-13.8-13.8.1-1 Show 4 moreShow less3.9.1-13.9.1-23.9.2-13.9.3-1 |
None known |
nltkDebian:13 |
All versions, plus listed versions 3.10.0-13.10.1-13.10.3-13.9.1-23.9.2-13.9.3-1 |
None known |
nltkDebian:14 |
All versions, plus listed versions 3.10.0-13.10.1-13.10.3-13.9.1-23.9.2-13.9.3-1 |
None known |
nltkPyPI |
before 3.10.4, plus listed versions 0.80.90.9.30.9.40.9.5 Show 45 moreShow less0.9.60.9.70.9.80.9.92.0.12.0.1rc12.0.1rc2-git2.0.1rc32.0.1rc42.0.22.0.32.0.42.0.52.0b42.0b52.0b62.0b72.0b82.0b93.0.03.0.0b13.0.0b23.0.13.0.23.0.33.0.43.0.53.13.10.03.10.13.10.23.10.33.23.2.13.2.23.2.33.2.43.2.53.33.43.4.13.4.23.4.33.4.43.4.5 |
3.10.4 |
nltkUbuntu:Pro:14.04:LTS |
All versions, plus listed versions 2.0~b9-0ubuntu42.0~b9-0ubuntu4.1~esm22.0~b9-0ubuntu4.1~esm42.0~b9-0ubuntu4.1~esm52.0~b9-0ubuntu4.1~esm6 |
None known |
nltkUbuntu:Pro:16.04:LTS |
All versions, plus listed versions 3.0.4-13.0.5-13.1-13.1-1ubuntu0.13.1-1ubuntu0.1+esm13.1-1ubuntu0.1+esm23.1-1ubuntu0.1+esm33.1-1ubuntu0.1+esm4 |
None known |
nltkUbuntu:Pro:18.04:LTS |
All versions, plus listed versions 3.2.4-13.2.5-13.2.5-1ubuntu0.13.2.5-1ubuntu0.1+esm13.2.5-1ubuntu0.1+esm23.2.5-1ubuntu0.1+esm33.2.5-1ubuntu0.1+esm4 |
None known |
nltkUbuntu:Pro:20.04:LTS |
All versions, plus listed versions 3.4.5-13.4.5-23.4.5-2ubuntu0.1~esm13.4.5-2ubuntu0.1~esm23.4.5-2ubuntu0.1~esm33.4.5-2ubuntu0.1~esm4 |
None known |
nltkUbuntu:Pro:22.04:LTS |
All versions, plus listed versions 3.5-13.6.5-13.6.7-13.7-13.7-1ubuntu0.1~esm13.7-1ubuntu0.1~esm2 |
None known |
nltkUbuntu:Pro:24.04:LTS |
All versions, plus listed versions 3.8.1-13.8.1-1ubuntu0.1~esm13.8.1-1ubuntu0.1~esm2 |
None known |
nltkUbuntu:26.10 |
All versions, plus listed versions 3.9.2-13.9.3-13.10.0-13.10.1-13.10.3-1 |
None known |
nltkUbuntu:Pro:26.04:LTS |
All versions, plus listed versions 3.9.1-23.9.1-2build13.9.2-13.9.2-1ubuntu0.1~esm13.9.2-1ubuntu0.1~esm2 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-109956 is a high vulnerability (CVSS 8.3) in Nltk, published October 13, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.32%, the 24th percentile. Fixed in nltk 3.10.4.
References
github.com/nltk/nltk/security/advisories/GHSA-j456-xh4h-cpf2
vulncheck.com/advisories/nltk-through-3.10.3-path-traversal-via-wekaclassifier-model-path
nvd.nist.gov/vuln/detail/CVE-2026-109956
github.com/advisories/GHSA-9fgx-q9j8-3j83
github.com/nltk/nltk/pull/3863
github.com/nltk/nltk/commit/6a17606293fe9d3ea1006f99dd3fed2e2b25d519
Show 7 moreShow less
github.com/nltk/nltk/releases/tag/v3.10.4
github.com/advisories/GHSA-j456-xh4h-cpf2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/109xxx/CVE-2026-109956.json
security-tracker.debian.org/tracker/CVE-2026-109956
github.com/nltk/nltk
ubuntu.com/security/CVE-2026-109956
cve.org/CVERecord?id=CVE-2026-109956
Left out here: 19 versions and the rest of 2 source descriptions. The JSON record has them.