Aug 13, 2026·8 min read·2 visits
A path traversal vulnerability in NLTK 3.9.4 allows remote unauthenticated attackers to read arbitrary files via percent-encoded path traversal sequences because lexical validation occurs before URL decoding.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
The Natural Language Toolkit (NLTK) is an open-source Python platform for natural language processing. It is widely used in server-side pipelines, academic research, and machine learning workflows to tokenise, parse, and process text. Among its core features is the ability to load external and internal datasets, corpora, and resource packages dynamically via helper utilities.
CVE-2026-12243 describes a high-severity path traversal vulnerability in NLTK version 3.9.4. This vulnerability resides in the data resource loading modules, specifically within functions like nltk.data.load() and nltk.data.find(). The flaw arises from an order-of-operations conflict where lexical security validation is executed before URL decoding.
Attackers with control over input parameters passed to these functions can construct payloads that bypass directory sandbox constraints. The vulnerability is classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / 'Path Traversal'). This allows unauthenticated users to read arbitrary files accessible to the application's runtime context.
The following diagram illustrates the flow of a malicious payload through the vulnerable parsing chain:
The root cause of CVE-2026-12243 lies in NLTK's sequence of input processing operations. To prevent path traversal, NLTK version 3.9.4 implemented a regular expression validation check using the pattern _UNSAFE_NO_PROTOCOL_RE in nltk/data.py. This regex scans incoming resource locator strings for the literal character sequence ../, absolute directory markers, and Windows drive roots.
While this regex intercepts direct traversal sequences, it does not evaluate equivalent percent-encoded directory patterns. If an input string containing percent-encoded sequences such as ..%2f is provided, the regex validator verifies the characters literally. Because the literal pattern ../ is absent from the raw string, the input successfully passes the initial lexical safety checkpoint.
Following validation, the application maps the input string to a local filesystem location. To achieve this, NLTK executes urllib.request.url2pathname(). This method performs standard URL decoding, converting hexadecimal sequences back to their ASCII character representations. The validation-cleared string ..%2f is translated into the directory separator ../ only after the safety verification mechanism has terminated.
Consequently, NLTK constructs and opens an active path traversal sequence on the target filesystem. Because there are no secondary canonicalisation or boundary validation checks after the decoding step, the application processes the decoded path. The file access occurs within the operational environment of the running Python process, permitting unauthorized reading of files outside the designated resource boundaries.
In the vulnerable implementation of NLTK 3.9.4, resource resolution relied on the direct parsing of input without post-decoding validation. This structure is visible in nltk/data.py where URL schemas are evaluated first and the local path resolution is performed via url2pathname() directly before loading. A patch was introduced in commit aec4fce1b84ad725b8975f7365b23a4f626572a9 (PR #3522) to address this systemic architectural flaw.
The patch introduces nltk/pathsec.py as a centralized I/O security module that performs absolute and relative sandboxing checks. This security layer verifies that any resolved filesystem access remains strictly bounded inside approved data paths. The validation routine converts both the input targets and the permitted root locations into resolved Path objects, evaluating their relationships after all decoding has completed.
Let us review the key parts of the security sentinel implemented in nltk/pathsec.py:
# Centrally handles path validation and prevents traversal escapes.
def validate_path(path_input, context="NLTK", required_root=None):
if isinstance(path_input, int) or not path_input or not str(path_input).strip():
return
try:
raw = path_input.path if hasattr(path_input, "path") else str(path_input)
if "://" in raw:
parsed = urlparse(raw)
if parsed.scheme in ("http", "https", "ftp"):
return
if parsed.scheme == "file":
raw = unquote(parsed.path)
# Resolve paths strictly to evaluate absolute references and symlinks
try:
target = Path(raw).resolve()
except (OSError, ValueError):
lower_raw = raw.lower()
if ".zip" in lower_raw:
zip_idx = lower_raw.find(".zip") + 4
target = Path(raw[:zip_idx]).resolve()
else:
target = Path(raw)
# LAYER 1: Scoped Sandbox Check
if required_root:
root_raw = required_root.path if hasattr(required_root, "path") else str(required_root)
scoped_root = Path(root_raw).resolve()
# Verify the path is within the required root directory
if not (target == scoped_root or target.is_relative_to(scoped_root)):
raise ValueError(f"Security Violation [{context}]: Path {target} escapes root {scoped_root}")
# LAYER 2: Global NLTK_DATA Sandbox Check
allowed_roots = _get_allowed_roots()
if any(target == root or target.is_relative_to(root) for root in allowed_roots):
return
# CWD Fallback (Requires explicit opt-in if ENFORCE is enabled)
try:
cwd = Path(os.getcwd()).resolve()
if target == cwd or target.is_relative_to(cwd):
if any(cwd == root for root in allowed_roots):
return
msg = "Security Violation: CWD access restricted in ENFORCE mode."
if ENFORCE:
raise PermissionError(msg)
else:
warnings.warn(f"Security Warning [{context}]: Path {target} allowed via CWD.", RuntimeWarning, stacklevel=3)
return
except (OSError, ValueError):
pass
msg = f"Security Violation [{context}]: Unauthorized path {target}"
if ENFORCE:
raise PermissionError(msg)
else:
warnings.warn(msg, RuntimeWarning, stacklevel=3)
except (PermissionError, ValueError):
raiseThe core loader integration inside nltk/data.py was altered to force decoded local paths through the path validation routines. By executing _secure_open and validating paths early in the call stack of _open(), the framework intercepts malicious traversal vectors before they are handled by underlying low-level system calls.
Exploitation of CVE-2026-12243 requires that an attacker have influence over the string parameter supplied to NLTK resource loading functions. This condition is common in NLP applications exposing web search interfaces, translation engines, or model-selection dropdowns. If the input is not pre-validated at the application layer, the parameter passes directly to the NLTK backend.
An attacker targeting a Linux environment can construct a path traversal payload containing percent-encoded sequences to read sensitive configurations. By submitting ..%2f..%2f..%2f..%2f..%2fetc/passwd, the request bypasses NLTK's regex, undergoes URL decoding to ../../../../../etc/passwd, and is resolved relative to the data search paths. The application then performs a file read operation, returning the file content to the response body.
This technique extends to retrieving runtime environmental variables and cloud credentials from internal endpoints. On containerized or cloud-hosted instances, sending ..%2f..%2f..%2f..%2f..%2fproc/self/environ retrieves active session tokens, database passwords, and API keys. Similarly, Windows targets are vulnerable to the retrieval of system parameters through payloads targeting directories such as ..%2f..%2f..%2f..%2f..%2fWindows/System32/drivers/etc/hosts.
The security impact of CVE-2026-12243 is classified as High, achieving a CVSS v3.0 Base Score of 7.5. The vulnerability affects the confidentiality of the system directly, allowing unauthenticated remote read access to files accessible to the application process. It does not provide immediate integrity or availability impacts, as the traversal mechanism is restricted to read operations.
The attack vector is network-based, requires no privileges, and demands no user interaction. Because NLTK is frequently deployed in API routing layers and multi-tenant pipelines, exploitation can lead to a compromise of adjacent application databases or cloud infrastructure if sensitive keys are recovered from local configuration files. This structural risk is amplified in microservice architectures where services run with elevated system permissions.
While EPSS data shows a low exploitation probability of 0.58% within thirty days, the presence of documented proof-of-concept indicators raises the likelihood of targeted exploitation. Organizations operating multi-tenant natural language systems must treat this as a critical exposure, particularly since upgrading NLTK without explicitly modifying configuration settings does not fully mitigate the risk.
Remediation of this vulnerability requires upgrading NLTK to version 3.9.5 or higher, where the centralized pathsec security framework is fully integrated. However, installing the patch is insufficient on its own due to the backward-compatibility architecture. By default, NLTK sets the validation mode parameter nltk.pathsec.ENFORCE to False, which limits the system to emitting warnings instead of blocking file read operations.
To achieve complete remediation, software developers must explicitly enable strict security enforcement within their codebase. This is accomplished by setting the enforcement toggle to True at the application entry point. This modification ensures that any unauthorized path traversal, SSRF, or Zip-slip attempt results in an immediate exception, stopping the file execution pipeline.
The following Python code illustrates the correct configuration sequence to secure NLTK resource loading operations:
import nltk
import nltk.pathsec
# Enable strict enforcement to block path traversal attempts
nltk.pathsec.ENFORCE = True
# If your application must access local resources in the working directory:
nltk.data.path.append('.') # Explicitly authorize current directory accessSecurity teams can monitor log files for specific path violation signatures to detect exploitation attempts. When NLTK runs in the default warn-only configuration, path traversal bypass attempts will generate a RuntimeWarning containing strings like Security Warning [pathsec.open]: Path or Security Violation. Configuring alert monitors to trigger on these specific string patterns allows teams to identify probing activities before enabling enforcement.
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
NLTK (Natural Language Toolkit) NLTK Project | 3.9.4 | 3.9.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network (AV:N) |
| CVSS Base Score | 7.5 |
| EPSS Score | 0.00583 (Percentile: 44.89%) |
| Impact | Arbitrary File Read / Information Disclosure |
| Exploit Status | Proof-of-Concept (PoC) documented |
| KEV Status | Not listed |
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.
CVE-2026-9318 is a stored cross-site scripting (XSS) vulnerability affecting Jazzband tablib versions prior to 3.10.0. The flaw is located in the HTML export functionality of multi-sheet Databook objects. Due to raw f-string interpolation, unsanitized sheet titles containing malicious script tags are rendered directly as HTML, allowing arbitrary client-side code execution in a victim's browser.