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

Symlink inside FramenetCorpusReader's frame/lu/fulltext dirs passes the CVE-2026-54292 name guard and reads arbitrary XML outside the corpus root.

- Payout: Research
- Severity: high
- Program: NLTK
- Found by (vendor credit): LiteshGhute
- Found by: LiteshGhute
- Disclosed: 2026-09-08
- CVE: CVE-2026-62384
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-f833-7jw8-xwrv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-f833-7jw8-xwrv
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:info-disclosure vuln:path-traversal

**Target:** Python library corpus reader (FramenetCorpusReader) file loading
**Root cause:** The fix for CVE-2026-54292 only rejects literal separators, '..' and drive prefixes in names, but the resulting path is resolved via self.abspath() (lexical join) and opened through PathPointer.open() without required_root, so symlinks are never resolved or scoped.
**Steps:**
1. Create corpus root with frame/ subdir and a secret XML file outside the corpus root.
2. Plant a symlink `<corpus_root>/frame/evil_link.xml` pointing at the outside secret file; name has no separators so `_reject_unsafe_path_component` passes.
3. Instantiate `FramenetCorpusReader(corpus_root, [])` and set `_frame_idx` to skip index build.
4. Call `reader.frame_by_name("evil_link")`; the outside file's contents are parsed and returned.
5. Repeat identically for `_lu_file()` (lu<id>.xml under lu/) and `doc()` (filename under fulltext/).
**How found:** Noticed the CVE-2026-54292 fix only validates name components and never resolves symlinks, then compared the three call sites (abspath + raw PathPointer.open, no required_root) against CorpusReader.open() and NKJPCorpusReader's correct use of validate_path(required_root=...).
**Exploitation:** Attacker with write access to a shared/tampered corpus directory plants a symlink with a benign separator-free name; any victim process making a routine frame_by_name/_lu_file/doc call reads and parses arbitrary XML outside the corpus root, no user interaction required.
**Insight:** When a path-traversal fix only sanitizes string components, test symlinks with separator-free names; check whether the sink uses a lexical join (abspath) versus the codebase's own scoped, symlink-resolving validator, and audit sibling call sites of the same guard for the same gap.

