highCVE-2026-62388Sep 2, 2026

CVE-2026-62388: NLTK pathsec Insecure Default Allows Security Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

NLTK's security module defaults to a warn-only mode, meaning path traversal and pickle deserialization protections silently allow attacks through instead of blocking them.

Packagenltk
Ecosystempip
Affected<= 3.9.4
Fixed in3.10.0
CVE-2026-62388: NLTK pathsec Insecure Default Allows Security Bypass

The problem

The pathsec module was added to NLTK as the fix for prior CVEs covering path traversal and pickle deserialization (CVE-2024-39705, CVE-2026-0846). It centralizes all security checks: file path validation, network URL filtering, and zip-slip guards.

However, the module reads ENFORCE from the environment variable NLTK_PATHSEC_ENFORCE and defaults to False when that variable is absent. Every one of its eight validation functions follows the same pattern: on a violation, if ENFORCE is False, it emits a RuntimeWarning and lets execution continue.

The security gates are opt-in, not opt-out. Any NLTK 3.9.x deployment expecting the prior CVE fixes to be active is still fully exploitable unless an operator manually set the env var.

Proof of concept

A working proof-of-concept for CVE-2026-62388 in nltk, with the exact payload below.

python
import nltk.pathsec
import warnings

# ENFORCE is False by default -- no env var set
print(f'ENFORCE = {nltk.pathsec.ENFORCE}')  # False

# pathsec.open() should block /etc/passwd, but only warns
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter('always')
    f = nltk.pathsec.open('/etc/passwd', 'r')
    print(f'Opened: {f.name}')          # /etc/passwd -- attack succeeds
    print(f'Warning: {w[0].category}')  # RuntimeWarning, not an exception
    print(f.read())                     # file contents returned to caller

The root cause is CWE-1188 (Insecure Default Initialization of Resource). Every validation function in pathsec.py has a hard branch: if ENFORCE: raise SecurityError(...) else warnings.warn(..., RuntimeWarning). Because ENFORCE defaults to False, the raise branch is never reached in a standard install, and execution falls through to the dangerous operation unconditionally.

The patch in commit 155e40343c (PR #3593, released in 3.10.0) flips the default so ENFORCE=True unless the operator explicitly sets NLTK_PATHSEC_ENFORCE=0. This converts the module from opt-in enforcement to opt-out, which is the correct fail-secure posture for a security control.

The fix

Upgrade to nltk 3.10.0 or later. The default is now ENFORCE=True; all security violations raise PermissionError without any environment variable configuration. If you need the old warn-only behavior for a controlled migration, set NLTK_PATHSEC_ENFORCE=0 explicitly.

On affected versions (<=3.9.4) where upgrading is not yet possible, set NLTK_PATHSEC_ENFORCE=1 or add import nltk.pathsec; nltk.pathsec.ENFORCE = True at application startup.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research