- Severity: HIGH
- CVE: CVE-2026-115878
- Ecosystems: pip
- Packages: stanza
## 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=””)` (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`:
“`python
# _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…
Source: GitHub Security Advisories · 2026-11-03