Sep 2, 2026·6 min read·4 visits
NLTK versions prior to 3.10.3 are vulnerable to local arbitrary code execution via an untrusted search path flaw when invoking the Graphviz 'dot' command.
An Untrusted Search Path (CWE-426) vulnerability exists in the Natural Language Toolkit (NLTK) library when executing the Graphviz 'dot' utility. Because the library fails to enforce absolute paths when executing external commands, local attackers can plant a malicious binary named 'dot' inside the current working directory. The library then executes the malicious binary, resulting in local arbitrary code execution under the context of the running Python process.
CVE-2026-78680 is an Untrusted Search Path (CWE-426) vulnerability identified in the Natural Language Toolkit (NLTK) library. This flaw resides in NLTK versions prior to 3.10.3. It allows local attackers to hijack execution flow and run arbitrary code by placing a malicious binary inside the application's current working directory or within a directory listed in a modified PATH environment variable.
The vulnerability is located within two distinct components: dependency graph visualization (nltk.parse.dependencygraph.dot2img) and sentence alignment rendering (nltk.translate.api.AlignedSent._repr_svg_). In both components, the library attempts to run the external Graphviz dot utility to render visual structures. However, it fails to specify absolute paths when spawning the process, causing the host operating system to resolve the executable dynamically.
On systems that search the current working directory first (such as Windows) or systems configured with a relative search path (like . in Unix-like OSs), this behavior allows local arbitrary code execution. The vulnerability is classified under CWE-426 (Untrusted Search Path) and CWE-427 (Use of Uncontrolled Search Path Element), indicating a lack of path enforcement during process execution.
The root cause of CVE-2026-78680 lies in how Python's standard library subprocess module processes bare command arguments on different operating systems. When an application passes a simple string name like "dot" to subprocess.run or subprocess.Popen without providing an absolute path, the underlying system API must resolve the actual file path.
On Windows operating systems, the standard binary search order prioritizes the current working directory (CWD) before looking in system-wide %PATH% directories. Consequently, if an NLTK script is executed in a folder containing an attacker-controlled file named dot.exe, dot.bat, or dot.cmd, the Windows loader executes that local file rather than the legitimate Graphviz binary located in the program files directory.
On Unix-like operating systems, a similar condition occurs if the environment's PATH variable is customized to contain a relative directory entry, such as a dot (.) or empty elements. This dynamic file resolution bypasses security boundaries because NLTK does not verify whether the resolved path points to a trusted directory before executing it.
An inspection of the vulnerable implementation in nltk/parse/dependencygraph.py reveals a critical design flaw in process invocation. Although the code called the validation function find_binary("dot") to locate the binary, the return value was discarded and never assigned to a variable.
# Vulnerable code structure in nltk/parse/dependencygraph.py
def dot2img(dot_string, t="svg"):
try:
find_binary("dot") # Validation result is discarded here
try:
if t in ["dot", "dot_json", "json", "svg"]:
proc = subprocess.run(
["dot", "-T%s" % t], # Bare command name remains
capture_output=True,
input=dot_string,
text=True,
)In the second vulnerable location, nltk/translate/api.py, the visualization method _repr_svg_ did not attempt to perform any path check. It directly executed ["dot", "-T%s" % output_format] using its bare name, allowing process resolution to look up the executable directly from the OS search path.
# Vulnerable code structure in nltk/translate/api.py
def _repr_svg_(self):
dot_string = self._to_dot().encode("utf8")
output_format = "svg"
try:
process = subprocess.Popen(
["dot", "-T%s" % output_format], # Direct bare name invocation
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)To exploit this vulnerability, an attacker must have write access to a directory where a victim executes an NLTK-dependent script, or trick the user into executing NLTK within an untrusted repository. The attacker creates a malicious file named dot (on Unix-like systems) or dot.bat/dot.exe (on Windows) in that workspace directory.
#!/bin/bash
# Malicious 'dot' payload placed in CWD
id > /tmp/compromised
exec /usr/bin/dot "$@"Once the payload is written and marked as executable, the victim runs a script that calls a rendering function like DependencyGraph.dot2img(). When the library triggers the subprocess execution, the operating system executes the local malicious file first. The script executes arbitrary shell commands under the privileges of the victim's Python process, potentially leading to system compromise.
The security impact of CVE-2026-78680 is classified as High. It yields a CVSS v3.1 base score of 7.8, reflecting high impact on confidentiality, integrity, and availability. Because execution occurs under the running Python process context, the exploit obtains the user's local security privileges.
In automated environments, such as continuous integration (CI) pipelines or automated data processing platforms, this vulnerability can facilitate local privilege escalation or container escape if the container process runs with elevated privileges. Similarly, in multi-user environments like Jupyter Notebook hubs, a malicious user could plant a payload to access other users' active work directories.
No mass exploitation has been identified in the wild, placing its EPSS score at 0.0012. However, because NLTK is highly prevalent in machine learning and data science stacks, security teams must proactively scan and remediate this dependency in operational environments.
The vulnerability is resolved in NLTK version 3.10.3. The patch captures the verified, absolute path returned by find_binary("dot") and passes it directly to the subprocess call. This circumvents the operating system's dynamic search path resolution and ensures that only the verified system binary is executed.
# Patched code path
try:
# Resolve to a trusted absolute path; find_binary refuses a
# CWD-relative match, preventing search path hijacking.
dot_binary = find_binary("dot")
except LookupError as e:
raise Exception("Cannot find the dot binary from Graphviz package") from e
try:
proc = subprocess.run(
[dot_binary, "-T%s" % t], # Uses absolute path
# ...
)To verify remediation, execute a script within a directory containing a mock dot executable. In safe versions, NLTK will reject the local relative binary and either throw an exception or execute the legitimate system Graphviz binary. If upgrading is not immediately possible, remove the current directory (.) from the environment's PATH variable and restrict write access to the working directories of automated pipelines.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
nltk NLTK Project | < 3.10.3 | 3.10.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-426, CWE-427 |
| Attack Vector | Local (L) |
| CVSS v3.1 Score | 7.8 (High) |
| CVSS v4.0 Score | 8.5 (High) |
| EPSS Score | 0.0012 (Percentile: 2.07%) |
| Exploit Maturity | Proof of Concept (PoC) |
| CISA KEV Status | Not Listed |
The application executes an external command with an untrusted or uncontrolled path, allowing attackers to place a malicious executable that hijacks process resolution.
An inconsistency in whitespace handling between the PCRE regex engine, PHP's native trim function, and web browsers allows unauthenticated attackers to bypass XSS protections in the league/commonmark AttributesExtension by injecting a Form Feed (U+000C) control character.
GHSA-JJV6-8J6V-6J52 details multiple algorithmic complexity issues in the SmartPunct and Attributes extensions of the league/commonmark PHP library, leading to high CPU consumption and Denial of Service (DoS) when parsing pathological Markdown inputs.
An algorithmic complexity vulnerability (CWE-407) in the AttributesExtension of league/commonmark allows unauthenticated remote attackers to cause CPU exhaustion and Denial of Service (DoS) via crafted Markdown payloads containing adjacent or consecutive attributes.
A stored Cross-Site Scripting (XSS) vulnerability exists in sanitize-html from version 1.9.0 up to 2.17.6. The flaw permits attackers to bypass scheme-policy enforcement using SVG SMIL animation elements targeting URL attributes with semicolon-separated URI lists.
An algorithmic complexity vulnerability in the python sqlparse library versions before 0.6.0 allows an attacker to cause high CPU usage and denial of service via a crafted SQL statement during formatting.
An infinite loop vulnerability in pypdf versions prior to 6.16.0 allows attackers to trigger computational resource exhaustion and complete thread locking by supplying a malformed PDF with a cyclic tree structure. When modifying or rewriting document outlines containing circular references, the library endlessly traverses /Next pointers, resulting in application denial of service.