Skip to content

GHSA-p3m8-78j2-g5p3 CVE-2026-62388 PYSEC-2026-3722

NLTK: Default ENFORCE=False Disables All pathsec Security Controls

High PyPI
Severity
0.0 / 10
High · CVSS 4.0
Exploit chance (EPSS)
0.51%
Chance of exploitation in the next 30 days. Higher than 43.71% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-1188 Initialization of a Resource with an Insecure Default
Fix
3.10.0
Upgrade nltk to this version or later

Description

NLTK's pathsec.py security module defaults to ENFORCE=False (line 24), which means all 8 security validation functions only emit RuntimeWarning instead of raising exceptions when violations are detected.

The pathsec module was introduced as the fix for CVE-2024-39705 (arbitrary code execution via pickle) and CVE-2026-0846 (path traversal). However, with ENFORCE=False as the default:

  1. pathsec.open('/etc/passwd') succeeds (reads the file, emits warning)
  2. pathsec.validate_network_url('http://169.254.169.254/...') succeeds (warning only)
  3. pickle.loads() via nltk.data.load() proceeds despite unsafe source (warning only)

Every security gate follows the same pattern:

ENFORCE = os.environ.get('NLTK_PATHSEC_ENFORCE', '').lower() in ('1', 'true', 'yes')

def validate_something(path):
    if is_violation(path):
        if ENFORCE:
            raise SecurityError('...')  # Only raised when env var is set
        else:
            warnings.warn('...', RuntimeWarning)  # Default: warning only
    # Execution continues regardless

This means the security remediations for CVE-2024-39705 and CVE-2026-0846 are effectively disabled by default. Any user who installed NLTK 3.9.x expecting the security fixes to be active is still vulnerable unless they manually set NLTK_PATHSEC_ENFORCE=1.

PoC:

import nltk.pathsec
import warnings

# Show that ENFORCE is False by default
print(f'ENFORCE = {nltk.pathsec.ENFORCE}')  # False

# Attempt to read /etc/passwd through pathsec -- should be blocked
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter('always')
    result = nltk.pathsec.open('/etc/passwd', 'r')
    print(f'File opened: {result.name}')  # /etc/passwd
    print(f'Warning emitted: {w[0].message}')  # RuntimeWarning (not an exception)
    # Attack succeeds -- file is readable

The correct default is fail-secure: ENFORCE should be True unless explicitly disabled. The current default makes the security module opt-in rather than opt-out, defeating its purpose.

Suggested fix: Change default to ENFORCE=True. Users who need backwards compatibility can set NLTK_PATHSEC_ENFORCE=0 to explicitly disable.

Affected versions

PyPI

PackageVulnerableFixed in
nltk
< 3.10.0
3.10.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

nltk PyPI → 3.10.0

pip
pip install --upgrade "nltk>=3.10.0"
poetry
poetry add "nltk>=3.10.0"
uv
uv add "nltk>=3.10.0"

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Attack vector
Network
Attack complexity
Low
Attack requirements
None
Privileges required
None
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

CVSS 3.1 7.5

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

Weakness

CWE-1188: Initialization of a Resource with an Insecure Default

The product initializes or sets a resource with a default that is intended to be changed by the product's installer, administrator, or maintainer, but the default is not secure.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-j456-xh4h-cpf2
NLTK: WekaClassifier passes an unvalidated caller-controlled model path to the weka JVM, bypassing nltk.pathsec containment
High Also affects nltk
GHSA-ff5c-cp5c-9wjf
NLTK: Uncontrolled resource consumption in RecursiveDescentParser via ambiguous or left-recursive grammars
Medium Also affects nltk
GHSA-f794-5jv7-7672
NLTK: Downloader.download follows hardlinks and overwrites outside-root files
High Also affects nltk
GHSA-8mgp-746c-j5xp
NLTK: Model-artifact APIs bypass pathsec and touch files outside allowed roots
High Also affects nltk
GHSA-8mpw-7fpc-4gqj
NLTK: Pl196xCorpusReader has quadratic ReDoS on malformed TEI blocks
Low Also affects nltk
GHSA-cw6x-m8jw-qmrh
NLTK: Uncontrolled recursion in nltk.featstruct.FeatStructReader causes unhandled RecursionError (DoS) via deeply nested feature-structure input
Medium Also affects nltk
GHSA-vp2x-qp44-57v7
NLTK: Quadratic CPU Exhaustion in `XMLCorpusView._read_xml_fragment()`
Low Also affects nltk
GHSA-ww6m-cw3f-q94g
NLTK: Quadratic-time DoS in PorterStemmer via long runs of 'y'
High Also affects nltk
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor