The patched release swaps torch.load for a wrapper that raises an error on anything below PyTorch 2.6, while ESPnet’s own package metadata still declares torch>=2.3.1 — and on those older builds a dozen remaining load calls are unsafe by default.
What happened
NVD published CVE-2026-90777 at 12:17 p.m. UTC on Sunday, September 13, 2026. The record describes it in a sentence: “ESPnet before 202609 deserializes pretrained model checkpoints using torch.load with weights_only=False, allowing arbitrary code execution from attacker-supplied files.”
ESPnet is an end-to-end speech processing toolkit — recognition, synthesis, enhancement, translation — and its normal workflow is to download somebody else’s pretrained checkpoint and run inference on it or fine-tune from it. That is the attack surface. The flaw is not reachable across the network against a running service; it is reachable the moment someone loads a model file from a hub.
The scoring comes entirely from VulnCheck, the CNA here. NVD carries two metrics from [email protected]: a CVSS v3.1 base score of 8.8 High, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H, marked Primary, and a CVSS v4.0 base score of 8.7 High, marked Secondary. The weakness is CWE-502, deserialization of untrusted data. There is no independent second assessment, and no source reports exploitation.
The UI:R in that vector reads on paper like a mitigating factor. Here the required interaction is running a recipe or an inference script against a downloaded checkpoint — not something a careless user might be tricked into, but the toolkit’s entire purpose.
GitHub advisory GHSA-64f6-3gqc-r926 was published on September 11, two days before the NVD record. It lists affected versions as 202511 and earlier, patched in 202609, and states the impact as “Arbitrary code execution as the user running the ESPnet training or inference process, triggered simply by loading an attacker-supplied pretrained model.” Credit goes to Jaime Ramírez, arexgodofwar, and Snakinya.
The vulnerable line is exactly where the advisory says it is. At tag v.202511, line 99 of espnet2/torch_utils/load_pretrained_model.py reads:
src_state = torch.load(path, map_location=map_location, weights_only=False)
At tag v.202609, the same statement, now at line 101, reads:
src_state = safe_torch_load(path, map_location=map_location)
That release shipped on September 2, 2026 — eleven days before the CVE record and nine days before the GitHub advisory. Anyone reading the v.202609 release notes the day they came out would have found no reason to treat the upgrade as urgent. The notes do not contain the phrase “remote code execution,” and they name no CVE. The change appears as one line among behavior changes: “safe_torch_load no longer falls back to unsafe loading automatically; it must be opted into explicitly.” The only place the notes discuss security at any length is a section on continuous integration permissions, which observes that “the repository default turned out to be write on almost everything, on jobs that check out and run pull request code.”
The fix has a precondition the package does not enforce
We read the espnet-202609 wheel from PyPI — the artifact NVD marks unaffected. Two things in it do not line up.
First, the new wrapper refuses to operate below PyTorch 2.6. espnet2/torch_utils/safe_torch_load.py checks the running version before doing anything else and raises:
raise RuntimeError(
"safe_torch_load requires PyTorch >= 2.6 for weights_only support. "
f"Found torch.__version__={torch.__version__}, "
"which is no longer supported by ESPnet."
)
Second, the same wheel’s metadata declares Requires-Dist: torch>=2.3.1. So the package asserts in its dependency list that PyTorch 2.3.1 is acceptable, and asserts in its security-critical code path that anything below 2.6 “is no longer supported by ESPnet.” A dependency resolver has no way to see the second statement. An environment that installs ESPnet 202609 alongside torch 2.4 or 2.5 satisfies the declared requirement completely, and every call into the fixed loading path raises a RuntimeError instead of loading a model.
That is the better half of the outcome. The worse half is the paths the fix did not touch. Searching the shipped wheel for torch.load( calls outside the wrapper returns thirteen. One passes the unsafe flag explicitly — line 160 of espnet2/beats/generate_beats_checkpoint.py:
pt_ckpt = torch.load(beats_pt_ckpt_path, map_location="cpu", weights_only=False)
The other twelve pass no weights_only argument, so they inherit PyTorch’s default. Several sit in code a user reaches normally: espnet2/bin/enh_inference.py, espnet2/bin/enh_tse_inference.py, the AVHuBERT and HuBERT encoders, and the uASR task. PyTorch changed that default to weights_only=True in version 2.6. On 2.6 and later, those twelve calls are safe because of a decision made upstream in PyTorch, not because of anything in this fix. On 2.3.1 through 2.5 — versions ESPnet’s own metadata still permits — the default is weights_only=False, and those calls do precisely what CVE-2026-90777 describes.
Put together: an installation that satisfies ESPnet’s stated dependencies but runs PyTorch below 2.6 gets a fixed path that throws and unfixed paths that execute attacker code. On that configuration, upgrading makes the software less usable without making it safer.
Why it matters
A machine learning checkpoint is a pickle, and a pickle is a program. The standard answers — weights_only=True, or the safetensors format — have existed for years. What CVE-2026-90777 documents is how long the old pattern survives inside a mature toolkit once it is load-bearing, and how partial the removal tends to be when it finally happens.
The interesting failure here is not that ESPnet had an unsafe torch.load. It is the shape of the remediation. A version range is a binary instrument: NVD says versions below 202609 are affected and 202609 and above are not, and there is no field in that record that can express “unless your PyTorch is older than 2.6, in which case the fix does not run and a dozen other call sites are live.” Every scanner reading that record will report a machine running ESPnet 202609 on torch 2.5 as remediated. It is not, and the record is not wrong so much as incapable of saying so.
This publication has covered several fixes in the past week that cover less than the flaw they close — a HAProxy patch that exists only as a commit, a GitLab release with no 18.x build carrying the fix. This one adds a variant: the fix exists, it is in a real release, and its effectiveness silently depends on the version of a transitive dependency the package does not pin. Anyone maintaining a requirements file has to know that to get the security benefit, and nothing in the advisory, the CVE record, or the release notes tells them.
The nine-day gap between the fix shipping and the advisory naming it is not misconduct — coordinated disclosure often works this way, and shipping the fix first is the right order. But it left the September 2 release notes as the only public description of a remote code execution fix for nine days, and they described it as a behavior change to a helper function. Organizations that triage upgrades by reading release notes had nothing to go on.
One detail deserves flagging as a thing to watch rather than a flaw. The wrapper still permits unsafe loading through three explicit opt-ins: allow_unsafe_fallback=True, the environment variable ESPNET_ALLOW_UNSAFE_TORCH_LOAD=1, or an interactive prompt. That is defensible, since legacy checkpoints have to remain loadable. The predictable failure mode is the environment variable landing in a Dockerfile to get one stubborn old checkpoint to load and staying there afterward, at which point the fix is off for every model that container ever touches.
What to do
Upgrade ESPnet to 202609 or later; the newest release on PyPI is 202609.post2, uploaded September 11, 2026.
Then check the PyTorch version in the same environment, because the upgrade is incomplete without it:
python -c "import espnet, torch; print(espnet.__version__, torch.__version__)"
If torch is below 2.6, pin it up yourself — torch>=2.6 — because ESPnet’s metadata will not. Below that line you have neither a working safe loader nor safe defaults on the remaining call sites.
Do not set ESPNET_ALLOW_UNSAFE_TORCH_LOAD=1 at the image or job level. If one legacy checkpoint genuinely needs the unsafe path, scope the variable to that single command.
Treat espnet2/beats/generate_beats_checkpoint.py as a separate case. It still passes weights_only=False explicitly in the shipped 202609 wheel, so it is unsafe on any PyTorch version. Run it only against checkpoints you produced or otherwise trust.
More broadly, treat third-party checkpoints as executables rather than data. Prefer safetensors where a model is published in that format, and do not fine-tune from a file pulled off a hub inside an environment that holds credentials you care about.
Sourcing note
Checked: the NVD record for CVE-2026-90777, including both CVSS metrics and their sources; GitHub Security Advisory GHSA-64f6-3gqc-r926; VulnCheck’s own advisory page; ESPnet’s source at tags v.202511 and v.202609, read directly from raw.githubusercontent.com; the espnet-202609 wheel downloaded from PyPI, including its METADATA and a full search for torch.load call sites; PyPI upload timestamps for every 2026 ESPnet release; and the GitHub release notes for v.202609.
The affected-line quotations, the Requires-Dist line, the RuntimeError text, and the count of thirteen remaining torch.load call sites were read out of the published artifacts, because no advisory states them.
cisa.gov returns 403 to automated fetching. We checked the cisagov/kev-data mirror instead: catalog version 2026.09.11, released September 11, 2026 at 7:32 p.m. UTC, 1,709 entries. CVE-2026-90777 is not in it, and no federal remediation deadline applies.
Unresolved: whether ESPnet intends to raise its declared PyTorch floor to match what safe_torch_load requires, and whether the twelve bare torch.load calls are in scope for a follow-up. No source reports exploitation, and VulnCheck’s advisory makes no exploitation claim. The severity assessment is single-source: VulnCheck is the CNA and supplied both scores.