Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

NLTK: Corpus readers follow symlinks outside trusted roots despite pathsec enforcementGHSA-p4rw-rvv2-7xwr

Fix: nltk/nltk@10d34b3

GHSA-p4rw-rvv2-7xwr is a Path Traversal vulnerability in nltk. A fix is available for nltk — see the affected versions and patch details below.

Also known asCVE-2026-79676PYSEC-2026-3737
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 8, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for GHSA-p4rw-rvv2-7xwr.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs37th percentile — riskier than 37% of all scored CVEsHighest risk
0.00%0.32%0.63%0.95%0.3%0.4%0.4%Sep 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
🐍nltk

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Several corpus readers still step outside NLTK's symlink-aware trusted-root model. They derive in-root paths from trusted corpus state, convert those paths back into plain strings, and reopen them with built-in open() rather than nltk.pathsec.open().

Details

  • Vulnerability type: Path traversal and symlink boundary bypass
  • Affected component: nltk.corpus.reader.ipipan, nltk.corpus.reader.crubadan, nltk.corpus.reader.lin
  • Affected versions: Published 3.9.4 and current source v3.10.0-rc2 both reproduced.
  • Patched versions: Not yet patched
  • Root cause: Root-derived paths are reopened with raw open() without preserving the trusted-root boundary.

IPIPANCorpusReader opens header.xml derived from morph.xml, CrubadanCorpusReader opens table.txt directly, and LinThesaurusCorpusReader opens simN.lsp paths returned from its own root helpers. Under pathsec.ENFORCE=True, a symlink placed inside the trusted corpus root can point outside the root and still be parsed successfully. It was confirmed parsed outside-root content is returned through public methods such as channels(), domains(), categories(), langs(), crubadan_to_iso(), synonyms(), and scored_synonyms().

PoC

Preconditions

  • The application processes attacker-influenced corpora inside a trusted NLTK data root or trusted corpus directory.

Steps

  1. Create a trusted corpus root and keep pathsec.ENFORCE=True with that root allowlisted.
  2. Place symlinked reader inputs such as header.xml, table.txt, or simN.lsp inside the root and point them to external files.
  3. Instantiate the corresponding corpus reader and call its normal public methods.
  4. Observe that parsed outside-root values are returned even though pathsec.open() blocks the same symlink targets.

Minimal reproducible excerpt

{'ipipan': ['LEAK', 'TOPSECRET', 'CLASSIFIED'], 'crubadan': ['LEAK'], 'lin': [('LEAK', 9.5)]}

Impact

An attacker who can stage corpus files or symlinks under a trusted data root can disclose outside-root content through normal corpus-reader results, defeating the boundary NLTK documents for shared and untrusted-input environments.

Remediation

Preserve PathPointer and required_root semantics end to end. Replace direct open() calls with nltk.pathsec.open() or a reader helper that keeps the trusted-root boundary intact.

References


Fix + full-codebase audit (verified)

I swept every raw file open in the corpus readers, not just the three the umbrella named:

ReaderSiteAdvisoryRoot scoping
crubadantable.txt + <code>-3grams.txtp4rw / j5pwrequired_root=self.root
linsimN.lspp4rwrequired_root=self.root
xmldocsXMLCorpusView bare-string fileid934p (base reader)global fallback (view has no root)
pl196xtextids indexfound by auditrequired_root=self._root
mteMTEFileReadermvf5required_root threaded through 8 call sites
toolboxStandardFormat.open codecs.opencr8cglobal sandbox (low-level parser)
named_entityload_ace_file ann/text7qj2global sandbox
nkjpXML_Tool source filep4rw classrequired_root=self._root

ipipan already validates via the earlier #3727 fix — unchanged.

Fix

Each site now calls nltk.pathsec.validate_path(path, required_root=…) before opening. Where the reader has a concrete corpus root, the check is scoped with required_root (rejects any escape outside that root). XMLCorpusView carries no root, so it falls back to the global data-root sandbox via getattr(self, "_root", None) — which also avoids an AttributeError on the bare-string path.

Reproduced (captured)

raw open(symlink) reads: 'TOPSECRET_OUTSIDE_ROOT'          <- the bypass
validate_path(symlink, required_root): ValueError -> BLOCKS the escape
validate_path(legit in-root): PASSED                       <- loads normally

Honest residual

The global-sandbox fallback (toolbox, named_entity, xmldocs-view) is only as tight as the allowed-roots list, which currently includes the system temp dir. Scoping every reader with required_root and removing the temp dir from the allowed roots would harden it further (separate advisory / task).

Tests

test_corpus_reader_pathsec.py — symlink escape rejected, in-root file allowed, XMLCorpusView string-fileid no AttributeError, MTEFileReader out-of-root rejected. 46 existing corpus/toolbox tests pass; all edited modules import (no circular import). pre-commit (black/isort/ruff) clean.


Scope caveat

validate_path blocks every symlink escape variant (verified) and equals pathsec.open()'s guarantee, but does NOT block hardlinks (no symlink to resolve; tracked separately as GHSA-f794-5jv7-7672) or the validate-then-open TOCTOU race (shared by pathsec.open; needs O_NOFOLLOW/openat).

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPInltkall versions3.10.3pip install --upgrade 'nltk==3.10.3'

Affected Products

1 product · 1 configurations
Application
nltknltk
< 3.10.3
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for nltk, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update nltk to 3.10.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-p4rw-rvv2-7xwr is resolved across your whole dependency graph.

  3. Workarounds

    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatModerate

A path traversal vulnerability exists in NLTK corpus readers that reopen root-derived paths using built-in open() instead of nltk.pathsec.open(). When processing corpus operations like channels(), domains(), or synonyms(), the application fails to enforce path restrictions on symbolic links within data roots. An…

Workaround published by Red Hat
Restrict local write permissions on NLTK data and corpus directories to prevent unauthorized staging of symlinked files, or ensure corpus file paths are validated against trusted roots before invoking corpus reader retrieval methods.
Source: Red Hat security advisory for GHSA-p4rw-rvv2-7xwr (CC BY 4.0)

Frequently Asked Questions

### Summary Several corpus readers still step outside NLTK's symlink-aware trusted-root model. They derive in-root paths from trusted corpus state, convert those paths back into plain strings, and reopen them with built-in `open()` rather than `nltk.pathsec.open()`. ### Details - **Vulnerability type:** Path traversal and symlink boundary bypass - **Affected component:** `nltk.corpus.reader.ipipan`, `nltk.corpus.reader.crubadan`, `nltk.corpus.reader.lin` - **Affected versions:** Published `3.9.4` and current source `v3.10.0-rc2` both reproduced. - **Patched versions:** Not yet patched - **R
O3 Security · Impact-Aware SCA

Is GHSA-p4rw-rvv2-7xwr in your dependencies?

Find it across PyPI, including transitive dependencies.

NLTK: Corpus readers follow symlinks outside trusted…