NLTK CVE-2026-78683 and CVE-2026-79657: safe pickle still executed code
TransitionParser used an unrestricted unpickler, then the first allowlist trusted whole modules. Analysis of RCE, versions 3.10.0 and 3.10.3, and NLP pipeline defence.
- AUTHOR
- Karol Rapacz / CEO Breachroad · OSCP · PNPT
- PUBLISHED
- 25 August 2026
- READING TIME
- 20 min read
- TOPIC
- AI Security
Two critical records published on 25 August show why a Python model file cannot be treated as passive data. CVE-2026-78683 covered unrestricted pickle deserialisation in TransitionParser.parse(). The first fix, available in NLTK 3.10.0, replaced it with an allowlisted loader. CVE-2026-79657 then showed that the allowlist trusted entire module namespaces containing functions that could still execute code. A complete fix requires NLTK 3.10.3.
The first record received CVSS 4.0 9.4 and CVSS 3.1 9.6, while the second received 9.3 and 9.8 respectively. Impact is critical, but the library does not itself expose a network endpoint for model upload. RCE appears when an application, notebook, pipeline or NLP service passes an attacker-controlled artifact to an affected loader.
Why pickle is an executable format
Python pickle stores instructions for reconstructing an object graph. A stream may identify classes and functions, while the REDUCE opcode directs the unpickler to call a callable with arguments. This can rebuild complex models, but it also means deserialising an unknown file resembles running a program more than parsing JSON.
A log warning does not change that property. In NLTK, pickle_load() could route data through WarningUnpickler, which announced risk but inherited standard pickle.Unpickler behaviour and did not block class resolution through find_class(). Code ran during load(), before an application could validate the returned object’s type.
Post-load validation is therefore too late. If a loader reconstructs an object and only then checks whether it is the expected model, a function invoked by the pickle has already produced its side effect. The safety boundary has to control globals and opcodes before they are used.
The first flaw: TransitionParser and an unrestricted loader
In releases through 3.9.4, TransitionParser.parse() opened a model file and called pickle_load() without restricted=True. A built-in RestrictedUnpickler existed, but the production parser path did not select it. A crafted artifact could therefore resolve any available Python function and execute it with the privileges of the NLTK process.
An attack requires untrusted data to reach the modelFile parameter. In a notebook, this might be a model downloaded from a publication link. In an MLOps pipeline, it may be an object in a bucket or registry writable by another team. In a web service, it can be a user upload or an identifier mapped to external storage. Merely having NLTK in the dependency graph does not prove a reachable path.
NLTK 3.10.0 moved the parser to allowlisted_pickle_load and added a separate helper for legacy Punkt artifacts. That was the correct direction: restrict reconstruction to symbols needed by a valid model. The remaining weakness was the breadth of the trust definition.
The second flaw: a namespace is not a security boundary
The first allowlist admitted modules through prefixes, including namespaces required by NumPy, SciPy and scikit-learn models. Punkt also allowed a broad portion of nltk.tokenize. The loader checked whether a declared name belonged to a trusted module, but not always whether the specific callable was a safe data constructor.
CVE-2026-79657 identified two classes of gadget. NLTK’s namespace exposed ReppTokenizer._execute, which launches a process. A function under numpy.f2py.crackfortran led to evaluation of a controlled expression. The pickle did not need to leave an allowed prefix; it selected a dangerous function inside it.
This is a general design failure. A module organises code but does not define a homogeneous trust domain. Classes representing arrays can sit beside functions that retrieve network content, open files, spawn subprocesses or interpret expressions. Re-exports add another problem: a dangerous symbol may be reachable through an apparently permitted path.
What 3.10.3 changes
Version 3.10.3, uploaded to PyPI on 12 August, is the first release covering both records. The changes reject dotted names that permit attribute traversal, block dangerous modules and callables, and narrow selected loaders to exact (module, qualname) pairs. For TransitionParser, the project built a list of concrete globals required by a legitimate SVC model instead of permitting all of NumPy, SciPy and sklearn.
The project also added tests for gadgets, re-exports, extension opcodes and legitimate models. That matters because a denylist without regression tests can become incomplete as dependencies evolve. A future NumPy or SciPy release may expose another function under a different name.
Administrators should move directly to 3.10.3 or a later supported version. Stopping at 3.10.0, 3.10.1 or 3.10.2 removes the original unrestricted loader but leaves the allowlist-bypass class described by the second CVE. Confirm the version in the effective runtime, not only requirements.txt: notebooks, workers and batch images can use different virtual environments.
Inventorying reachability
Search for calls to TransitionParser.parse, allowlisted_pickle_load, punkt_pickle_load and local wrappers around NLTK. Determine where every file originates: the application package, controlled registry, shared storage, upload, URL or notebook directory. Then establish who can change the object and whether it is pinned to an immutable digest.
Do not limit review to .pickle or .pkl extensions. A filename does not determine format, and a model artifact may be inside an archive, carry a generic name or arrive through a manifest. Review user caches and image layers too, because upgrading the library does not delete previously downloaded, unapproved models.
An SBOM should identify the NLTK version, but an AI artifact manifest is also necessary: source, owner, revision, hash, format and loader. Without that data, a team can answer which library ran but not which byte stream it deserialised.
Detection and response
At the process layer, look for unusual children of Python, shell execution, network tools and interpreters during model loading. Correlate them with artifact retrieval, job startup and parser logs. Outbound connections before actual NLP processing, changes in the working directory and secret access by a short-lived worker also deserve attention.
If an untrusted pickle was loaded by an affected release, scanning only the file for known strings is insufficient. Pickle can assemble a graph in many ways, while legitimate dependency functions provide a broad gadget surface. Treat the process as potentially compromised, preserve the artifact and logs, isolate the workload, and identify which secrets and services it could access.
After confirmed code execution, rebuild the worker from a clean image, rotate reachable credentials and inspect shared cache. Rotate only after persistence is stopped. If a notebook held a cloud or registry token, review use of that identity outside the NLP host as well.
Designing a safe model pipeline
The safest choice is a data format with no call semantics. Where compatibility requires pickle, use an exact allowlist of classes and functions, reject unknown opcodes, limit size, and perform import in an isolated process without secrets or broad egress. The positive list should be derived from a real approved model and protected by tests.
Artifact promotion should resemble code promotion. A research environment may propose a model, scanning and review determine its format and symbols, and production retrieves only a pinned digest from an internal registry. This cannot remove every parser bug, but it prevents arbitrary replacement by an end user.
Source facts and Breachroad conclusions
Affected versions, loader methods, unrestricted-pickle mechanism, broad-allowlist bypass, function classes and CVSS scores come from the CVE records and project advisories. Package dates for 3.10.0 and 3.10.3 come from PyPI and NLTK tags. The sources do not report active exploitation of these two CVEs.
The inventory model, process monitoring, artifact manifest and isolation guidance are Breachroad conclusions. AI and MLOps security training helps teams recognise executable formats, while application and API security assessments can evaluate uploads, registries, loaders and worker identity.


