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.
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?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| PyTorch | pip | <= 3.10.2 | 3.10.3 |
Do you use PyTorch? You're affected.
How severe is it?
What should I do?
1 step-
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_loadwith broadallowed_modulesvalues and tighten them to exact safe globals.
What does CISA's SSVC say?
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:
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
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
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.
References
- github.com/advisories/GHSA-x99w-6fgc-pmfw
- github.com/nltk/nltk/commit/c3e37113742a1ebeeb4f2ca58941f320f98805ea
- github.com/nltk/nltk/releases/tag/v3.10.3
- github.com/nltk/nltk/security/advisories/GHSA-x99w-6fgc-pmfw
- github.com/pypa/advisory-database/tree/main/vulns/nltk/PYSEC-2026-3735.yaml
- nvd.nist.gov/vuln/detail/CVE-2026-79657
- vulncheck.com/advisories/nltk-before-3.10.3-remote-code-execution-via-unsafe-pickle-deserialization
Timeline
Related Vulnerabilities
CVE-2024-5452 9.8 pytorch-lightning: RCE via deepdiff Delta deserialization
Same package: torch CVE-2023-43654 9.8 TorchServe: SSRF + RCE via unrestricted model URL loading
Same package: torch CVE-2022-45907 9.8 PyTorch: RCE via unsafe eval in JIT annotations
Same package: torch CVE-2022-0845 9.8 pytorch-lightning: code injection enables full RCE
Same package: torch CVE-2024-35198 9.8 TorchServe: URL bypass enables arbitrary model loading
Same package: torch