← Back to Feed CACHED · 2026-10-08 01:17:32 · CACHE_KEY CVE-2026-62384
CVE-2026-62384 · CWE-22 · Disclosed 2026-08-22

NLTK FramenetCorpusReader Symlink-Based Sandbox Bypass (bypasses CVE-2026-54292 fix)

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like leaving a booby-trapped shortcut in someone's filing cabinet — but you need the office key first

CVE-2026-62384 is a symlink-based sandbox escape in NLTK's FramenetCorpusReader, affecting versions 3.10.0 through 3.10.1. It bypasses the _reject_unsafe_path_component() fix shipped for CVE-2026-54292 by using symlinks whose names contain no path separators. When frame_by_name(), _lu_file(), or doc() resolve such a symlink via self.abspath() (a plain lexical join), they follow it outside the corpus root and return the contents of arbitrary XML-parseable files. The fix in 3.10.2 routes these paths through the symlink-resolving CorpusReader.open() with required_root. CWE-22 and CWE-59 both apply.

The vendor CVSS of 7.5 HIGH with vector AV:N/AC:L/PR:N/UI:N dramatically overstates the real threat. The chain *requires* filesystem write access to the corpus directory to plant the symlink — that is not network/unauthenticated access; it is local-privileged access. The sibling vuln CVE-2026-62383 in IPIPANCorpusReader was correctly scored at 5.5 MEDIUM with AV:L/PR:L. The same logic applies here. Additionally, FrameNet is a niche academic linguistics corpus — the fraction of NLTK's 65M monthly installs that actually instantiate FramenetCorpusReader is vanishingly small, and even when exploited the impact is confidentiality-only, limited to files that pass XML parsing. No integrity, no availability, no code execution.

"Filesystem write needed to plant symlinks guts the remote-unauthenticated claim. Library-only, read-only, niche component."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attain filesystem write access to the corpus directory

The attacker must be able to create files (specifically symlinks) inside the directory tree that NLTK's FramenetCorpusReader treats as its corpus root. This typically means the attacker already has local shell access, has compromised an upload path that writes into the corpus directory, or has poisoned a corpus archive (tar/zip preserving symlinks) before download.
Conditions required:
  • Local or application-level write access to the NLTK corpus data directory
  • Operating system that supports symbolic links (Linux/macOS; limited on Windows without SeCreateSymbolicLinkPrivilege)
Where this breaks in practice:
  • Corpus directories are usually read-only in production; NLTK data is typically installed once via nltk.download() and not user-writable
  • An attacker who already has filesystem write access to the host has far more impactful options than reading XML files through NLTK
  • Windows default ACLs restrict symlink creation to administrators or Developer Mode
Detection/coverage: Host-based file integrity monitoring (FIM) on corpus directories would flag new symlink creation. AIDE, OSSEC, or EDR filesystem telemetry can detect this.
STEP 02

Plant a symlink with a clean name pointing outside the corpus root

The attacker creates a symbolic link inside the FrameNet corpus subdirectory (e.g., frame/) whose filename contains no /, \, .., or drive-letter prefix. The symlink's target is an arbitrary path like /etc/passwd or /app/config/secrets.xml. The _reject_unsafe_path_component() guard passes the name because it only checks the *name string*, not the resolved destination.
Conditions required:
  • Symlink creation privilege on the target filesystem
  • Knowledge of the absolute path to a target file that is XML-parseable
Where this breaks in practice:
  • Target file must be valid XML or the NLTK XML parser will throw an ElementTree.ParseError and return nothing useful
  • Many high-value secrets (private keys, .env files, database credentials) are NOT valid XML — drastically limiting exfiltration value
  • Container/chroot environments may prevent symlink resolution outside the container filesystem namespace
STEP 03

Trigger a read via FramenetCorpusReader API

The attacker (or an application processing attacker-influenced input) calls frame_by_name(), _lu_file(), or doc() with an argument matching the planted symlink name. The function resolves the path through self.abspath(), follows the symlink, and returns parsed XML content from outside the corpus root.
Conditions required:
  • An application or interactive session that calls the vulnerable FramenetCorpusReader methods
  • The method argument must match the symlink filename
Where this breaks in practice:
  • In most deployments, FramenetCorpusReader is called with hardcoded or internally-sourced frame names, not user-supplied input
  • An attacker who can both write symlinks AND control API arguments already has extensive access to the host
  • The FrameNet API is used almost exclusively in academic NLP research, not in internet-facing services
Detection/coverage: Application-level logging of FramenetCorpusReader method calls would show unexpected frame name lookups. Python audit hooks (sys.addaudithook) can intercept open() calls to out-of-scope paths.
STEP 04

Exfiltrate parsed XML content

The attacker retrieves the parsed XML data returned by the corpus reader method. In a local exploitation scenario, they read it directly. In a web-application scenario (the vendor's implied threat model), the parsed data would need to be reflected back to the attacker through the application's response — requiring the application to expose FrameNet query results to external users.
Conditions required:
  • A channel to observe the return value of the corpus reader call
  • If remote: the application must reflect parsed corpus data in its HTTP response
Where this breaks in practice:
  • Web applications almost never expose raw NLTK FrameNet query results to end users
  • Even if reflected, the parsed XML structure may mangle or truncate the target file content
  • Rate limiting and output encoding on web apps further reduce data exfiltration throughput
Detection/coverage: WAF rules or output filtering detecting unexpected XML structures in API responses. Network DLP monitoring for sensitive file content patterns.
03 · Compensating Control

1
MEDIUM 4.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade NLTK to ≥ 3.10.2 — The definitive fix. Version 3.10.2 routes frame_by_name(), _lu_file(), and doc() through CorpusReader.open() with required_root, which resolves symlinks before validating the path. Pin nltk>=3.10.2 in your requirements.txt / pyproject.toml. No mitigation SLA applies at MEDIUM — go straight to the 365-day noisgate remediation SLA.
2
MEDIUM 4.5→LOW 2.5
SEVERITY REDUCED
Make corpus directories read-only and owned by root — Set corpus data directories (nltk_data/corpora/framenet_v17/) to chmod 755 owned by root:root (or equivalent restrictive ACLs on Windows). This prevents unprivileged users from planting symlinks. Applies to all hosts running NLTK in your fleet.
3
MEDIUM 4.5→LOW 2.0
SEVERITY REDUCED
Strip symlinks from any externally-sourced corpus archives — If your pipeline ingests corpus data from external sources, unpack archives with --no-same-owner and add a post-extraction step that removes symlinks: find /path/to/corpus -type l -delete. This breaks step 1 of the attack path for the supply-chain variant.
4
MEDIUM 4.5→IGNORE 0.5
SEVERITY REDUCED
Run NLTK workloads in containers with read-only root filesystem — Deploy NLP pipeline containers with --read-only flag and mount corpus data as read-only volumes. Container namespace isolation also prevents symlink resolution to host filesystem paths outside the container. This breaks both the symlink-planting and symlink-resolution steps.
What doesn't work
  • WAF / network-layer controls — NLTK is a local Python library, not a network service. There is no HTTP request to inspect or block. WAF rules are irrelevant.
  • Python virtual environment isolation — venvs isolate package installations but do NOT restrict filesystem reads. A symlink inside a venv's corpus dir still resolves to arbitrary paths on the host.
  • Upgrading to NLTK 3.10.1 — This version is still vulnerable. The fix is in 3.10.2, not 3.10.1. The 3.10.1 release addressed unrelated issues.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone observed. Not listed on CISA KEV. No campaigns, threat actor TTPs, or incident reports reference this CVE as of 2026-10-08.
Proof-of-Concept AvailabilityNo public PoC repositories found. Checked pocindex.io, GitHub (no repos named CVE-2026-62384), ExploitDB, and Nuclei templates. The GHSA advisory (GHSA-f833-7jw8-xwrv) describes the technique conceptually but no weaponized exploit code is circulating.
EPSS0.00651 (0.65%) — well below the 10th percentile threshold for likely exploitation. Consistent with a library-level bug that requires local access.
KEV StatusNot listed. No CISA KEV entry. No BOD 22-01 remediation deadline applies.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (vendor: 7.5 HIGH). noisgate assessment: the AV:N/PR:N claims are incorrect. Exploitation requires filesystem write access to plant symlinks, making the realistic vector AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N (5.5 MEDIUM), matching the score NVD assigned to sibling CVE-2026-62383.
Affected VersionsNLTK ≥ 3.10.0, < 3.10.2. Versions prior to 3.10.0 are affected by the predecessor CVE-2026-54292 instead (different path traversal, fixed in 3.10.0). Versions ≤ 3.9.x are not affected by *this specific* symlink bypass.
Fixed VersionNLTK 3.10.2 (commit 736d321, PR #3726). Also available in 3.10.3+.
Install Base / ExposureNLTK has ~65 million monthly PyPI downloads. However, FramenetCorpusReader is an obscure academic component — the FrameNet corpus must be separately downloaded via nltk.download('framenet_v17'). No Shodan/GreyNoise/Censys exposure signatures exist because NLTK is a library, not a network service.
Disclosure Timeline2026-08-22 disclosed, 2026-09-08 GHSA published. Bypasses the fix for CVE-2026-54292 (patched 2026-05 in 3.10.0). Part of a cluster of NLTK corpus reader symlink issues (CVE-2026-62383, CVE-2026-62384, CVE-2026-62385, CVE-2026-79676).
ReporterNot publicly attributed in the GHSA advisory. Likely the same researcher(s) who reported CVE-2026-54292 and the related corpus reader symlink cluster.

Sources.

  1. GHSA-f833-7jw8-xwrv — GitHub Security Advisory
  2. NVD — CVE-2026-62384
  3. OSV — CVE-2026-62384
  4. GitLab Advisory Database — CVE-2026-62384
  5. OpenCVE — CVE-2026-62384
  6. NLTK Fix Commit 736d321
  7. PyPI — NLTK Package
  8. DailyCVE — CVE-2026-62384 Analysis
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (4.5/10)

Why this verdict

  • Misscored attack vector: The vendor vector claims AV:N/PR:N (network, no privileges), but planting a symlink in the corpus directory requires *local filesystem write access* — the realistic vector is AV:L/PR:L, which alone drops the CVSS from 7.5 to 5.5. This matches the scoring of the identical sibling vuln CVE-2026-62383.
  • Compound prerequisite friction: The attacker must (1) have filesystem write to the corpus dir, (2) know a target file path that is valid XML, and (3) either directly invoke the API or control input to an app that calls it. An attacker with filesystem write has far more impactful options available — this vuln adds marginal value.
  • XML-only exfiltration: The corpus reader parses the target file as XML via ElementTree. Non-XML files (private keys, .env, database creds, password hashes) cause a parse error and leak nothing. This drastically narrows the confidentiality impact compared to an unrestricted file read.
  • Niche component within a popular package: NLTK's 65M monthly downloads are impressive, but FramenetCorpusReader requires a separate 200MB corpus download and is used almost exclusively in linguistics research. The fraction of NLTK installs that instantiate this reader in production is negligible.
  • Role multiplier: NLTK is deployed on data-science workstations (low-value), ML pipeline application servers (typical), and occasionally in RAG/LLM preprocessing tiers (typical). It is *not* canonically a high-value-role component — it is not an identity provider, hypervisor, network edge appliance, backup system, CI/CD runner, or domain controller. In ML pipeline tiers, exploitation yields read access to XML files on that server — blast radius is host-level at worst, not domain/fleet/supply-chain scale. No high-value role floor applies.
  • No exploitation signals: Zero in-the-wild activity, no KEV listing, EPSS 0.65%, no public PoC code. The attack is theoretical with no evidence of weaponization.

Why not higher?

The vulnerability is confidentiality-only with no code execution, no integrity impact, and no availability impact. It requires local filesystem write access — an attacker position that already implies significant compromise. The affected component (FramenetCorpusReader) is an obscure academic corpus reader within NLTK, not a widely-exercised API surface. NLTK is not deployed in any high-value infrastructure role where the blast radius would justify a HIGH floor.

Why not lower?

NLTK's enormous install base (65M/month) means even a niche component has a non-trivial absolute number of affected installations. The fix-bypass nature (circumventing CVE-2026-54292's patch) indicates a systemic code pattern issue that could recur. And in the specific scenario where a web application processes user-uploaded corpus archives that preserve symlinks, the attack chain shortens — though this remains a contrived deployment pattern. A LOW rating would understate the risk for the small population of NLP applications that do process external corpus data.

06 · Verification

Crowdsourced verification payload.

Run on any host where NLTK is installed. Execute with python3 check_cve_2026_62384.py — no special privileges required. The script checks the installed NLTK version and whether the vulnerable FramenetCorpusReader code path exists.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
"""CVE-2026-62384 checker — NLTK FramenetCorpusReader symlink sandbox bypass.
Outputs VULNERABLE / PATCHED / UNKNOWN with exit codes 1 / 0 / 2.
"""
import sys

try:
    import nltk
except ImportError:
    print("UNKNOWN — nltk is not installed on this host.")
    sys.exit(2)

version = getattr(nltk, '__version__', None)
if version is None:
    print("UNKNOWN — could not determine nltk version.")
    sys.exit(2)

try:
    from packaging.version import Version
except ImportError:
    # Fallback: simple tuple comparison
    parts = tuple(int(x) for x in version.split('.')[:3])
    if (3, 10, 0) <= parts < (3, 10, 2):
        print(f"VULNERABLE — nltk {version} is in the affected range [3.10.0, 3.10.2).")
        sys.exit(1)
    elif parts >= (3, 10, 2):
        print(f"PATCHED — nltk {version} contains the fix.")
        sys.exit(0)
    else:
        print(f"PATCHED — nltk {version} predates the vulnerable code (3.10.0+). Note: check for CVE-2026-54292/CVE-2026-62385 on pre-3.10.0 versions.")
        sys.exit(0)
else:
    v = Version(version)
    if Version('3.10.0') <= v < Version('3.10.2'):
        print(f"VULNERABLE — nltk {version} is in the affected range [3.10.0, 3.10.2).")
        sys.exit(1)
    elif v >= Version('3.10.2'):
        print(f"PATCHED — nltk {version} contains the fix.")
        sys.exit(0)
    else:
        print(f"PATCHED — nltk {version} predates the vulnerable code (3.10.0+). Note: check for CVE-2026-54292/CVE-2026-62385 on pre-3.10.0 versions.")
        sys.exit(0)