
Docker lab demonstrating CVE-2026-12243 path traversal in NLTK before 3.10.0, contrasting vulnerable and patched behavior with a synthetic secret in an isolated, offline environment.
A small, reproducible Docker lab that contrasts the percent-encoded path traversal behavior in NLTK 3.9.4 with the corrected behavior in NLTK 3.10.0.
The vulnerable version validates the resource name before URL decoding. The
fixed payload %2e%2e/%2e%2e/outside/lab-secret.txt passes the check, is later
decoded to ../../outside/lab-secret.txt, and escapes both the configured
NLTK data directory and the process working directory. The patched version
rejects the same input.
[!CAUTION] This repository is for defensive education and authorized testing only. The lab uses a fixed synthetic secret inside an isolated container. Do not adapt it to access systems or data you do not own or have explicit permission to test.
| Container | NLTK version | Expected result |
|---|---|---|
vulnerable | 3.9.4 | Reads the synthetic marker outside /lab/nltk_data |
patched |
No corpus download, server, port, bind mount, or runtime internet connection is used.
docker compose)run.shgit clone https://github.com/morzelowski/CVE-2026-12243-NLTK-PoC.git
cd CVE-2026-12243-NLTK-PoC
docker compose build
./run.sh
Expected result:
=== Vulnerable image: NLTK 3.9.4 ===
NLTK version : 3.9.4
Working directory : /app
Configured data dir: /lab/nltk_data
Encoded resource : %2e%2e/%2e%2e/outside/lab-secret.txt
Decoded resource : ../../outside/lab-secret.txt
Resolved candidate : /outside/lab-secret.txt
NLTK warning : Security Violation [pathsec.open]: Unauthorized path /outside/lab-secret.txt
NLTK result : read 36 bytes
Synthetic marker : CVE-2026-12243-SYNTHETIC-LAB-MARKER
[PASS] Traversal escaped the configured NLTK data directory.
=== Patched image: NLTK 3.10.0 ===
NLTK version : 3.10.0
NLTK result : blocked (ValueError: Unsafe resource path: '...')
[PASS] Patched version rejected the same encoded traversal.
Lab completed: traversal reproduced and patched rejection verified.
The affected image reports that /outside/lab-secret.txt is unauthorized but
still returns its contents because NLTK 3.9.4 uses warning-only path security
by default. Exception and warning text can vary slightly; run.sh uses the
process exit status, not a fragile text match, to decide whether the
demonstration passed.
Each image contains this deliberately separated layout:
/
├── app/
│ └── poc.py # process CWD is /app
├── lab/
│ └── nltk_data/ # configured NLTK search root
└── outside/
└── lab-secret.txt # public synthetic marker
poc.py replaces nltk.data.path with /lab/nltk_data and passes the fixed
resource name %2e%2e/%2e%2e/outside/lab-secret.txt to
nltk.data.load(..., format="raw"). In the affected release, decoding occurs
too late in the validation flow:
/lab/nltk_data
↓ ../../outside/lab-secret.txt
/outside/lab-secret.txt
Because the working directory is /app, the canary is outside both locations
that matter to the demonstration: /app and /lab/nltk_data.
The script has two explicit assertions:
Any other result exits non-zero, so automated checks cannot report a false success.
The Compose configuration intentionally limits the lab:
network_mode: none disables networking while either demonstration runs.no-new-privileges enabled.noexec temporary filesystem
is writable.fixtures/lab-secret.txt, a public test marker
copied into the image during build.The image build needs package-index access to install the two pinned NLTK versions. Runtime execution is offline.
nltk.data.load() or
nltk.data.find()The lab pins 3.9.4 because it is the concrete release named in the original report and 3.10.0 because it is the first patched release recorded by the maintainer advisory.
Upgrade NLTK and keep it current:
python -m pip install "nltk>=3.10.0"
Also avoid passing untrusted resource names into filesystem-loading APIs. Where user selection is required, map user-facing identifiers to an allowlist of application-owned resources instead of accepting paths or URLs directly.
Run either side independently:
docker compose run --rm vulnerable
docker compose run --rm patched
Inspect the fully resolved Compose configuration:
docker compose config
Remove the locally built lab images:
docker compose down --rmi local
permission denied: ./run.sh — run chmod +x run.sh once.docker: command not found — install Docker Desktop or Docker Engine.docker compose build --no-cache.aec4fceReleased under the MIT License.
3.10.0| Blocks the encoded traversal |