NLTK: Model-artifact APIs bypass pathsec and touch files outside allowed roots
CVE-2026-81726 · GHSA-8mgp-746c-j5xp · PYSEC-2026-3740 · published 2 Sept 2026
How serious is it
- Severity: high, CVSS score 8.3 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-81726?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
nltk(PyPI) <= 3.10.3
How to check
- Run
pip show nltkin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
nltkto 3.10.4 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
Several model-artifact APIs still treat caller-controlled model paths as ordinary filenames even when NLTK path security is enforced. The same outside-root paths are rejected by guarded helpers, but these public read and write flows still use raw file APIs.
### Details
- **Vulnerability type:** File sandbox bypass
- **Affected component:** `TransitionParser.train`, `TransitionParser.parse`, `AveragedPerceptron.save`, `AveragedPerceptron.load`, `PerceptronTagger.save_to_json`, `save_maxent_params`
- **Affected versions:** Published `3.9.4` and current source `v3.10.0-rc2` both reproduced.
- **Patched versions:** Not yet patched
- **Root cause:** Model import and export helpers use built-in `open()` on caller-controlled paths instead of pathsec-aware helpers.
`TransitionParser.train()` writes outside allowed roots, `TransitionParser.parse()` reads outside allowed roots, `AveragedPerceptron` bypasses the sandbox in both directions, and adjacent read-side helpers in the same family already show the intended guarded behavior. I confirmed outside-root reads and writes while `pathsec.open()` or the guarded sibling helpers rejected the same paths.
### PoC
**Preconditions**
- The application enables `pathsec` enforcement and lets untrusted workflows choose model import or export paths.
**Steps**
1. Enable `pathsec.ENFORCE=True` and restrict allowed roots to a dedicated sandbox directory.
2. Use public model import or export APIs with paths that point outside that root.
3. Observe the same paths are rejected by negative-control guarded helpers such as `pathsec.open()`, `PerceptronTagger.load_from_json()`, or `load_maxent_params()`.
4. Observe the vulnerable APIs still read or write outside-root files successfully.
**Minimal reproducible excerpt**
```text
transition_train_exists True
transition_parse_loader_read_bytes 13
averaged_load_keys ['bias']
maxent_save wrote ['alwayson.tab', 'labels.txt']
```
### Impact
Consumers that rely on `pathsec` for local containment can be tricked into reading or overwriting files outside approved roots through normal model persistence and loading APIs.
### Remediation
Route all model-path file access through `nltk.pathsec.open()` or existing pathsec-aware helpers, and add regression tests that pair each vulnerable API with a negative control on the same path.
### References
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/parse/transitionparser.py#L551-L571
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tag/perceptron.py#L106-L113
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tag/perceptron.py#L262-L273
- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/classify/maxent.py#L1583-L1602
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/nltk/nltk/security/advisories/GHSA-8mgp-746c-j5xp
- nvd.nist.gov/vuln/detail/CVE-2026-81726
- github.com/nltk/nltk/pull/3757
- github.com/nltk/nltk/pull/3759
- github.com/nltk/nltk/pull/3813
- github.com/nltk/nltk/commit/2a92b71827d754ae8920261e7ed0c4bb283ab2d7
- github.com/nltk/nltk/commit/a44a7af69bca87e92d9c4a701fcbbe4512e8d450
- github.com/nltk/nltk/commit/cbc98458b43de5f792f0382583c16df39e5c5117
- github.com/pypa/advisory-database/tree/main/vulns/nltk/PYSEC-2026-3740.yaml
- www.vulncheck.com/advisories/nltk-through-3.10.3-path-traversal-via-model-artifact-apis
- github.com/advisories/GHSA-8mgp-746c-j5xp
- github.com/nltk/nltk
Merged from GitHub Advisory Database, OSV. Last updated 3 Nov 2026. All vulnerabilities · RSS · JSON