CVE-2026-78683
NLTK before 3.10.0 Remote Code Execution via Unsafe Pickle Deserialization
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.4
NLTK before 3.10.0 Remote Code Execution via Unsafe Pickle Deserialization
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
CVE-2026-78683- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - CVSS 3.1
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
EUVDEUVD-2026-65229
9.4
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
EUVD-2026-65229- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-gx65-c5hj-vpv5
9.4
Duplicate Advisory: [CWE-502] Unsafe Pickle Deserialization in TransitionParser Allows Remote Code Execution
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-rhp5-r9x4-f5g2. This link is maintained to preserve external references.
Original Description
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
GHSA-gx65-c5hj-vpv5- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-rhp5-r9x4-f5g2
9.4
NLTK: Unsafe Pickle Deserialization in TransitionParser Allows Remote Code Execution
Summary
The NLTK library's TransitionParser.parse() method deserializes model files using pickle_load() with the default restricted=False parameter, allowing arbitrary Python code execution when loading a malicious model file. The library provides a RestrictedUnpickler class for safe deserialization, but it is never used by production code paths, leaving the vulnerability unpatched.
Root Cause
File: nltk/parse/transitionparser.py (lines 542-557)
The parse() method calls pickle_load(f) without restricted=True, routing through WarningUnpickler which inherits from pickle.Unpickler and does NOT override find_class(). This allows arbitrary class/function resolution during unpickling, enabling RCE via standard pickle gadgets (e.g., os.system, subprocess.Popen).
Vulnerability chain in nltk/picklesec.py:
def pickle_load(file, *, context=None, restricted=False):
if restricted:
return RestrictedUnpickler(file).load() # Safe: blocks all globals
return WarningUnpickler(file, context=context).load() # VULNERABLE PATH
WarningUnpickler only emits a warning but does NOT block unsafe class loading — it calls super().load() which is standard pickle.Unpickler.load().
Why this is not by design:
- NLTK intentionally created
RestrictedUnpicklerto block unsafe deserialization - The
restricted=Trueparameter exists in the API but is never used by any production code path - All call sites use the default
restricted=False:transitionparser.py:557,parse/chartparser_app.py:816,parse/chartparser_app.py:2273,parse/chartparser_app.py:2311
Attack Surface
Entry point: TransitionParser().parse(depgraphs, modelFile) receives a filesystem path with no validation.
Exploitation path:
- Attacker places a malicious pickle file at a known or attacker-controlled location
- Victim calls
parser.parse(sentences, "/path/to/malicious_model.pkl") pickle_load()deserializes the file withrestricted=False(default)- Standard pickle gadget chain executes arbitrary Python code with victim's privileges
Impact: Remote code execution with the privileges of the user running the NLTK-dependent application. Affects researchers, data scientists, and automated ML pipelines using NLTK for parsing tasks.
Steps to Reproduce
Environment
- NLTK version: 3.8.1+ (all versions with
transitionparser.py) - Python 3.6+
- No special dependencies required
Reproduction
-
Create a malicious pickle file that uses
__reduce__to execute a system command during deserialization. -
Call
TransitionParser().parse([], '/path/to/malicious_model.pkl'). -
The
pickle_load(f)call attransitionparser.py:557usesrestricted=Falseby default, routing throughWarningUnpickler, which does not overridefind_class()and permits full class resolution — executing the embedded gadget. -
Arbitrary code executes with the victim's privileges.
Proof That the Fix Works
Changing line 557 in transitionparser.py from:
model = pickle_load(f)
to:
model = pickle_load(f, restricted=True)
causes RestrictedUnpickler to raise an UnpicklingError and block execution, confirming the safe path prevents the attack.
Working PoC
import pickle
import os
from nltk.parse.transitionparser import TransitionParser
# Create malicious pickle with RCE payload
class Exploit:
def __reduce__(self):
return (os.system, ('touch /tmp/nltk_poc_triggered',))
with open('/tmp/malicious_model.pkl', 'wb') as f:
pickle.dump(Exploit(), f)
# Trigger the vulnerable code path (requires algorithm argument in ≤ 3.9.4)
parser = TransitionParser('arc-standard') # or 'arc-eager'
parser.parse([], '/tmp/malicious_model.pkl') # loads and unpickles unsafely
# Exploit succeeds: file /tmp/nltk_poc_triggered is created
On NLTK ≥ 3.10.0 (patched), the same code fails with:
_pickle.UnpicklingError:…
- Record
GHSA-rhp5-r9x4-f5g2- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-502
- Updated
- 2026-11-02
NVD
9.4
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
CVE-2026-78683- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSV
9.4
NLTK before 3.10.0 Remote Code Execution via Unsafe Pickle Deserialization
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
CVE-2026-78683- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-502
- Updated
- 2026-11-02
Show 5 moreShow less
OSVDEBIAN-CVE-2026-78683
9.4
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
DEBIAN-CVE-2026-78683- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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-gx65-c5hj-vpv5
9.4
Duplicate Advisory: [CWE-502] Unsafe Pickle Deserialization in TransitionParser Allows Remote Code Execution
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-rhp5-r9x4-f5g2. This link is maintained to preserve external references.
Original Description
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
GHSA-gx65-c5hj-vpv5- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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
- 9.6 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVGHSA-rhp5-r9x4-f5g2
9.4
NLTK: Unsafe Pickle Deserialization in TransitionParser Allows Remote Code Execution
Summary
The NLTK library's TransitionParser.parse() method deserializes model files using pickle_load() with the default restricted=False parameter, allowing arbitrary Python code execution when loading a malicious model file. The library provides a RestrictedUnpickler class for safe deserialization, but it is never used by production code paths, leaving the vulnerability unpatched.
Root Cause
File: nltk/parse/transitionparser.py (lines 542-557)
The parse() method calls pickle_load(f) without restricted=True, routing through WarningUnpickler which inherits from pickle.Unpickler and does NOT override find_class(). This allows arbitrary class/function resolution during unpickling, enabling RCE via standard pickle gadgets (e.g., os.system, subprocess.Popen).
Vulnerability chain in nltk/picklesec.py:
def pickle_load(file, *, context=None, restricted=False):
if restricted:
return RestrictedUnpickler(file).load() # Safe: blocks all globals
return WarningUnpickler(file, context=context).load() # VULNERABLE PATH
WarningUnpickler only emits a warning but does NOT block unsafe class loading — it calls super().load() which is standard pickle.Unpickler.load().
Why this is not by design:
- NLTK intentionally created
RestrictedUnpicklerto block unsafe deserialization - The
restricted=Trueparameter exists in the API but is never used by any production code path - All call sites use the default
restricted=False:transitionparser.py:557,parse/chartparser_app.py:816,parse/chartparser_app.py:2273,parse/chartparser_app.py:2311
Attack Surface
Entry point: TransitionParser().parse(depgraphs, modelFile) receives a filesystem path with no validation.
Exploitation path:
- Attacker places a malicious pickle file at a known or attacker-controlled location
- Victim calls
parser.parse(sentences, "/path/to/malicious_model.pkl") pickle_load()deserializes the file withrestricted=False(default)- Standard pickle gadget chain executes arbitrary Python code with victim's privileges
Impact: Remote code execution with the privileges of the user running the NLTK-dependent application. Affects researchers, data scientists, and automated ML pipelines using NLTK for parsing tasks.
Steps to Reproduce
Environment
- NLTK version: 3.8.1+ (all versions with
transitionparser.py) - Python 3.6+
- No special dependencies required
Reproduction
-
Create a malicious pickle file that uses
__reduce__to execute a system command during deserialization. -
Call
TransitionParser().parse([], '/path/to/malicious_model.pkl'). -
The
pickle_load(f)call attransitionparser.py:557usesrestricted=Falseby default, routing throughWarningUnpickler, which does not overridefind_class()and permits full class resolution — executing the embedded gadget. -
Arbitrary code executes with the victim's privileges.
Proof That the Fix Works
Changing line 557 in transitionparser.py from:
model = pickle_load(f)
to:
model = pickle_load(f, restricted=True)
causes RestrictedUnpickler to raise an UnpicklingError and block execution, confirming the safe path prevents the attack.
Working PoC
import pickle
import os
from nltk.parse.transitionparser import TransitionParser
# Create malicious pickle with RCE payload
class Exploit:
def __reduce__(self):
return (os.system, ('touch /tmp/nltk_poc_triggered',))
with open('/tmp/malicious_model.pkl', 'wb') as f:
pickle.dump(Exploit(), f)
# Trigger the vulnerable code path (requires algorithm argument in ≤ 3.9.4)
parser = TransitionParser('arc-standard') # or 'arc-eager'
parser.parse([], '/tmp/malicious_model.pkl') # loads and unpickles unsafely
# Exploit succeeds: file /tmp/nltk_poc_triggered is created
On NLTK ≥ 3.10.0 (patched), the same code fails with:
_pickle.UnpicklingError:…
- Record
GHSA-rhp5-r9x4-f5g2- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVPYSEC-2026-3734
9.4
NLTK before 3.10.0 (affected versions <=3.9.4) contains an unsafe pickle deserialization vulnerability in the TransitionParser.parse() method (nltk/parse/transitionparser.py). The method calls pickle_load() with the default restricted=False, routing deserialization through WarningUnpickler, which does not override find_class() and therefore permits arbitrary class resolution. When an application loads an attacker-crafted model file, embedded pickle gadget chains execute arbitrary Python code with the privileges of the user running the application. NLTK provides a RestrictedUnpickler for safe deserialization, but it is not used by production code paths. Fixed in 3.10.0.
- Record
PYSEC-2026-3734- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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
OSVMGASA-2026-0340 No score
Updated python-nltk packages fix security vulnerabilities
Updated packages fix security vulnerabilities. Please see the links.
- Record
MGASA-2026-0340- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
nltkPyPI |
through 3.9.4 | 3.10.0 |
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 |
before 3.10.0-1, plus listed versions 3.9.1-23.9.2-13.9.3-1 |
3.10.0-1 |
nltkPyPI |
before 3.10.0, 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.23.2.13.2.23.2.33.2.43.2.53.33.43.4.13.4.23.4.33.4.43.4.53.53.5b13.63.6.1 |
3.10.0 |
python-nltkMageia:10 From an advisory covering 18 CVEs |
before 3.10.3-1.mga10 | 3.10.3-1.mga10 |
python-nltkMageia:9 From an advisory covering 18 CVEs |
before 3.10.3-1.mga9 | 3.10.3-1.mga9 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| affected | ||
| affected, aliases, published, references | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-78683 is a critical vulnerability (CVSS 9.4) in Nltk, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.51%, the 43rd percentile. Fixed in nltk 3.10.0.
References
github.com/nltk/nltk/security/advisories/GHSA-rhp5-r9x4-f5g2
vulncheck.com/advisories/nltk-before-remote-code-execution-via-unsafe-pickle-deserialization
nvd.nist.gov/vuln/detail/CVE-2026-78683
github.com/advisories/GHSA-gx65-c5hj-vpv5
github.com/nltk/nltk/pull/3631
github.com/nltk/nltk/commit/f26b3753038d937b68145daf15e9636f8451053c
Show 9 moreShow less
github.com/nltk/nltk/releases/tag/v3.10.0
github.com/pypa/advisory-database/tree/main/vulns/nltk/PYSEC-2026-3734.yaml
github.com/advisories/GHSA-rhp5-r9x4-f5g2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/78xxx/CVE-2026-78683.json
security-tracker.debian.org/tracker/CVE-2026-78683
github.com/nltk/nltk
advisories.mageia.org/MGASA-2026-0340.html
bugs.mageia.org/show_bug.cgi?id=36186
github.com/nltk/nltk/releases
Left out here: 15 versions and the rest of 2 source descriptions. The JSON record has them.