CVE-2026-79657: NLTK: pickle allowlist bypass enables RCE

GHSA-x99w-6fgc-pmfw CRITICAL CISA: ATTEND
Published September 8, 2026
CISO Take

NLTK's 'safe' pickle loaders — the ones introduced specifically to replace unrestricted pickle deserialization — can still be bypassed to achieve remote code execution, because the allowlist trusts entire module namespaces (nltk.tokenize, numpy) instead of exact safe callables, exposing dangerous gadgets like ReppTokenizer._execute (subprocess.Popen) and numpy.f2py.crackfortran.myeval (eval). This matters because NLTK sits behind 24,877 downstream dependents and any pipeline that loads an attacker-supplied tokenizer or parser artifact — a common pattern in NLP preprocessing and model-serving workflows — inherits this false sense of safety. There's no CISA KEV listing, no public exploit or scanner template yet, and EPSS is low in absolute terms (0.01214) though in the top 33rd percentile, so this is not yet under active exploitation but the low OpenSSF score (6.4/10) and 46 prior CVEs in the package point to a track record of security debt. Patch to nltk 3.10.3 immediately in any environment that deserializes tokenizer/model pickles from untrusted or third-party sources; until patched, treat NLTK's pickle loaders as unsafe and gate artifact ingestion behind out-of-band integrity checks (signing, hash allowlisting) rather than relying on the library's own allowlist.

Sources: NVD GitHub Advisory EPSS CISA KEV OpenSSF ATLAS

What is the risk?

Rated critical by the advisory due to unauthenticated arbitrary code execution, but real-world exploitation likelihood is currently moderate-to-low: EPSS is only 0.01214 in absolute terms (though top-33rd-percentile relative to the broader CVE population), there is no CISA KEV listing, no public PoC exploit, and no Nuclei template. The vulnerability requires an application to actively pass attacker-controlled pickle data into `punkt_pickle_load()` or `TransitionParser.parse()` — a common but not universal pattern — so exposure depends heavily on whether an organization's pipeline ingests third-party tokenizer/model artifacts. Given NLTK's massive install base (24,877 dependents) and the fact that this specifically defeats a security control marketed as the safe replacement for raw pickle loading, the risk should be treated as high-priority for any AI/NLP pipeline that deserializes external artifacts, even absent current in-the-wild exploitation.

How does the attack unfold?

Malicious artifact distribution
Attacker crafts a pickle payload whose REDUCE callable targets an in-namespace dangerous function (ReppTokenizer._execute or numpy.f2py.crackfortran.myeval) and disguises it as a legitimate NLTK tokenizer or parser model artifact.
AML.T0010.001
Trusted deserialization
Victim application loads the artifact through NLTK's supposedly hardened loaders (punkt_pickle_load or TransitionParser.parse), which allowlist entire module namespaces instead of exact safe callables.
AML.T0011.000
Code execution
During unpickling, the REDUCE opcode invokes the dangerous gadget, triggering subprocess execution or an eval() call before any post-load type validation occurs.
AML.T0050
Host compromise
The attacker achieves arbitrary code execution on the host running the NLP pipeline, enabling data exfiltration, credential theft, or lateral movement into connected training/serving infrastructure.
AML.T0018.002

What systems are affected?

Package Ecosystem Vulnerable Range Patched
PyTorch pip <= 3.10.2 3.10.3
103.0K OpenSSF 6.4 25.0K dependents Pushed 6d ago 14% patched ~224d to patch Full package profile →

Do you use PyTorch? You're affected.

How severe is it?

CVSS 3.1
N/A
EPSS
1.2%
chance of exploitation in 30 days
Higher than 67% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Moderate
Exploitation Confidence
medium
CISA SSVC: Public PoC
Composite signal derived from CISA KEV, VulnCheck KEV, CISA SSVC, EPSS, Metasploit, Exploit-DB, trickest/cve, Nuclei templates, and inthewild.io exploitation reports.

What should I do?

1 step
  1. Upgrade to NLTK 3.10.3 or later, which replaces broad module-prefix allowlists with exact (module, qualname) pairs and adds a hard denylist backstop (os, subprocess, sys, builtins, numpy.f2py, nltk.tokenize.repp) even under permissive allowlists. Until patched, do not load punkt/tokenizer or transition-parser pickle artifacts from any source you do not fully control — treat all such files as executable code, not data. Where external artifacts are unavoidable, verify integrity via cryptographic signing/hashing before deserialization, run the deserialization step in a sandboxed/isolated environment with no outbound network or filesystem access beyond scratch space, and monitor for anomalous subprocess spawns or eval calls originating from NLTK-importing processes. Audit any internal code that calls allowlisted_pickle_load with broad allowed_modules values and tighten them to exact safe globals.

What does CISA's SSVC say?

Decision Attend
Exploitation poc
Automatable Yes
Technical Impact total

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, Robustness and Cybersecurity
ISO 42001
Annex A.6 - AI System Data and Data Quality / Third-Party Relationships
NIST AI RMF
MANAGE-4.1 - Third-Party Risk Monitoring
OWASP LLM Top 10
LLM05 - Supply Chain Vulnerabilities

Frequently Asked Questions

What is CVE-2026-79657?

NLTK's 'safe' pickle loaders — the ones introduced specifically to replace unrestricted pickle deserialization — can still be bypassed to achieve remote code execution, because the allowlist trusts entire module namespaces (nltk.tokenize, numpy) instead of exact safe callables, exposing dangerous gadgets like ReppTokenizer._execute (subprocess.Popen) and numpy.f2py.crackfortran.myeval (eval). This matters because NLTK sits behind 24,877 downstream dependents and any pipeline that loads an attacker-supplied tokenizer or parser artifact — a common pattern in NLP preprocessing and model-serving workflows — inherits this false sense of safety. There's no CISA KEV listing, no public exploit or scanner template yet, and EPSS is low in absolute terms (0.01214) though in the top 33rd percentile, so this is not yet under active exploitation but the low OpenSSF score (6.4/10) and 46 prior CVEs in the package point to a track record of security debt. Patch to nltk 3.10.3 immediately in any environment that deserializes tokenizer/model pickles from untrusted or third-party sources; until patched, treat NLTK's pickle loaders as unsafe and gate artifact ingestion behind out-of-band integrity checks (signing, hash allowlisting) rather than relying on the library's own allowlist.

Is CVE-2026-79657 actively exploited?

No confirmed active exploitation of CVE-2026-79657 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-79657?

Upgrade to NLTK 3.10.3 or later, which replaces broad module-prefix allowlists with exact (module, qualname) pairs and adds a hard denylist backstop (os, subprocess, sys, builtins, numpy.f2py, nltk.tokenize.repp) even under permissive allowlists. Until patched, do not load punkt/tokenizer or transition-parser pickle artifacts from any source you do not fully control — treat all such files as executable code, not data. Where external artifacts are unavoidable, verify integrity via cryptographic signing/hashing before deserialization, run the deserialization step in a sandboxed/isolated environment with no outbound network or filesystem access beyond scratch space, and monitor for anomalous subprocess spawns or eval calls originating from NLTK-importing processes. Audit any internal code that calls `allowlisted_pickle_load` with broad `allowed_modules` values and tighten them to exact safe globals.

What systems are affected by CVE-2026-79657?

This vulnerability affects the following AI/ML architecture patterns: NLP preprocessing pipelines, model serving, training pipelines.

What is the CVSS score for CVE-2026-79657?

No CVSS score has been assigned yet.

What is the AI security impact?

Affected AI Architectures

NLP preprocessing pipelinesmodel servingtraining pipelines

MITRE ATLAS Techniques

AML.T0010.001 AI Software
AML.T0011.000 Unsafe AI Artifacts
AML.T0018.002 Embed Malware
AML.T0050 Command and Scripting Interpreter

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: Annex A.6
NIST AI RMF: MANAGE-4.1
OWASP LLM Top 10: LLM05

What are the technical details?

Original Advisory

### Summary The current source tree still allows arbitrary code execution during supposedly safer allowlisted pickle loading. The allowlist trusts whole module namespaces instead of exact safe globals, so crafted pickles can invoke dangerous in-namespace callables through pickle REDUCE. ### Details - **Vulnerability type:** Remote code execution via unsafe deserialization - **Affected component:** `nltk.picklesec.allowlisted_pickle_load`, `nltk.tokenize.punkt.punkt_pickle_load`, `nltk.parse.transitionparser.TransitionParser.parse` - **Affected versions:** Current source `v3.10.0-rc2`; published `3.9.4` was not the claim target for this bypass. - **Patched versions:** Not yet patched - **Root cause:** Module-prefix allowlists include dangerous callables such as `nltk.tokenize.repp.ReppTokenizer._execute` and `numpy.f2py.crackfortran.myeval`. `punkt_pickle_load()` allowlists both `nltk.tokenize.punkt` and the whole `nltk.tokenize` namespace, which exposes `ReppTokenizer._execute()` and its `subprocess.Popen(...)` sink during unpickling. `TransitionParser.parse()` uses `allowlisted_pickle_load(..., allowed_modules=("numpy", "scipy", "sklearn"))`, which permits `numpy.f2py.crackfortran.myeval()` and its attacker-controlled `eval(...)` path. I confirmed both gadgets create marker files before the caller returns or later aborts on type misuse. ### PoC **Preconditions** - The application loads an attacker-controlled tokenizer or model artifact through these public loaders. **Steps** 1. Create a pickle whose REDUCE callable is `ReppTokenizer._execute` and point its command to a harmless marker-file write. 2. Pass that payload to `punkt_pickle_load(BytesIO(payload))` and observe the marker file is created during unpickling. 3. Create a second pickle whose REDUCE callable is `numpy.f2py.crackfortran.myeval` and load it through `TransitionParser.parse()`. 4. Observe the second marker file is created before `TransitionParser.parse()` later fails on the returned object type. **Minimal reproducible excerpt** ```text {'punkt_marker': 'PUNKT_RCE', 'transitionparser_marker': 'TP_RCE'} ``` ### Impact Any caller that trusts these current allowlisted loaders can still execute attacker-controlled commands while loading model or tokenizer artifacts. This defeats the protection mechanism that replaced unrestricted pickle loading and creates a dangerous false sense of safety. ### Remediation Replace broad module-prefix allowlists with exact `(module, qualname)` pairs for the few safe classes or functions genuinely required. Do not allow entire namespaces such as `nltk.tokenize` or `numpy`, and keep post-load type validation only as a secondary defense. ### Resources - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tokenize/punkt.py#L120-L134 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tokenize/repp.py#L111-L115 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/parse/transitionparser.py#L26-L30 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/parse/transitionparser.py#L565-L571 --- ## Fix + attack demonstration (verified) + tightened callers `find_class` now, before the allowlists: 1. **Rejects any dotted `name`** → closes 4489 with zero legit impact. 2. **Denies dangerous modules** (`os`, `subprocess`, `sys`, `builtins`, `numpy.f2py`, `nltk.tokenize.repp`, …) even under a broad `allowed_modules` — a defense-in-depth **backstop** so a future too-broad allowlist can't silently reopen RCE. 3. **`builtins` denied wholesale**; safe primitives (`int`, `str`, …) must be named exactly via `allowed_globals`. Callers tightened: punkt drops the broad `nltk.tokenize` (keeps `nltk.tokenize.punkt` + exact `collections.defaultdict`/`builtins.int`); transitionparser keeps numpy/scipy/sklearn (array unpickling needs their submodules) with the new guards blocking the gadgets. ## Full pickle-sink audit Every deserialization sink in the tree was reviewed: **no raw `pickle.load`** anywhere, and **no** joblib/numpy/torch/dill/yaml/marshal loaders. `data.load` + `wordnet_app` use `RestrictedUnpickler` (blocks all globals — safe); the remaining `pickle_load` sites (`chartparser_app`, `tbl/demo`) load user-selected or self-written files and keep their warning. ## Attack demonstration (captured; fork clone) ``` === EXPLOITS blocked === 4489 sklearn.os.system (dotted) -> BLOCKED x99w numpy.f2py.crackfortran.myeval -> BLOCKED x99w nltk.tokenize.repp._execute -> BLOCKED backstop os.system (os allowlisted) -> BLOCKED backstop builtins.eval (exact global)-> BLOCKED === LEGIT loads still work === punkt round-trip via punkt_pickle_load -> OK builtins.int (safe primitive) -> OK ``` ## Tests `test_pickle_allowlist_security.py` — added 5 regressions (dotted traversal, both namespace gadgets, denied-module backstop, legit round-trip). Suite: 122 passed / 9 skipped (sklearn-dependent) across pickle/punkt/transition/tokenize. pre-commit (black/isort/ruff) clean.

Exploitation Scenario

An attacker publishes a poisoned NLTK-compatible tokenizer or dependency-parser model file to a public model hub, package registry, or shared internal artifact store, disguised as a legitimate punkt tokenizer or transition-parser model. A data scientist or an automated ML pipeline downloads and loads the artifact via `punkt_pickle_load()` or `TransitionParser.parse()`, believing these 'safe' loaders protect against pickle-based RCE. During deserialization, the pickle's REDUCE opcode invokes `ReppTokenizer._execute()` (spawning a subprocess) or `numpy.f2py.crackfortran.myeval()` (running `eval()`), both of which are in-namespace but dangerous callables the allowlist failed to exclude — giving the attacker code execution on the host before the load even completes or is validated.

Weaknesses (CWE)

CWE-502 — Deserialization of Untrusted Data: The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.

  • [Architecture and Design, Implementation] If available, use the signing/sealing features of the programming language to assure that deserialized data has not been tainted. For example, a hash-based message authentication code (HMAC) could be used to ensure that data has not been modified.
  • [Implementation] When deserializing data, populate a new object rather than just deserializing. The result is that the data flows through safe input validation and that the functions are safe.

Source: MITRE CWE corpus.

Timeline

Published
September 8, 2026
Last Modified
September 8, 2026
First Seen
September 8, 2026

Related Vulnerabilities