
Loading

Loading
We use strictly necessary cookies to run this site, and analytics cookies to understand how it's used. See our Privacy Policy for details.
A GitHub-reviewed security advisory, not necessarily under active exploitation the way our CISA KEV advisories are, but a real, disclosed vulnerability in an open-source dependency.
Affected packages
Vulnerable: >= 3.10.0, <= 3.10.3
Patched in 3.10.4
Details
BllipParser in nltk/parse/bllip.py hands caller-supplied filesystem paths to the native bllipparser C extension without routing them through NLTK's pathsec validation. The constructor parameters parser_model, reranker_features, and reranker_weights, and the alternative from_unified_model_dir() entry point, flow directly to the C extension's load_parser_model() and load_reranker_model() calls, which open and read the given path at the OS level. Any application that passes an attacker-influenced path to this API reads a file of the attacker's choosing from anywhere on the filesystem, outside the NLTK data roots that every other NLTK tool wrapper confines access to.
As of NLTK 3.10.0, SECURITY.md documents a centralized I/O security module (nltk.pathsec) with strict enforcement enabled by default, under which file-backed resources must resolve inside allowed NLTK data directories. For paths handed to external tools and native extensions that open files themselves, the module provides validate_tool_path(), which runs name-safety checks, rejects option-shaped, URL-shaped, and traversal inputs, opens the path with O_NOFOLLOW requiring a regular single-linked file, and returns the validated string the caller must use. Every sibling tool wrapper applies it. BllipParser applies none of it: the module imports only ParserI, safe_print, and Tree, with zero pathsec references, so the three model-path parameters reach the C extension unchecked. The violated invariant is that NLTK tool wrappers must bound caller-supplied native-tool paths to the data roots before the native code opens them.
nltk/parse/bllip.py:125-131 BllipParser.__init__ accepts parser_model,
reranker_features, reranker_weights (no validation)
nltk/parse/bllip.py:164 self.rrp.load_parser_model(parser_model, ...) -> C extension open
nltk/parse/bllip.py:166-170 self.rrp.load_reranker_model(features_filename=...,
weights_filename=...) -> C extension open
nltk/parse/bllip.py:218-255 from_unified_model_dir(model_dir) -> __init__ (no validation)
nltk/parse/bllip.py:9-11 imports: ParserI, safe_print, Tree (no pathsec import)
An application that uses BllipParser (requiring the optional bllipparser package to be installed) and that passes a model path derived from untrusted input to the constructor or to from_unified_model_dir() allows that input to designate an arbitrary filesystem path. The native extension opens and reads the designated file into process memory to parse it as model data. This defeats the pathsec data-root confinement that NLTK applies to all other native-tool model loads. The attacker-controlled path can target files outside the NLTK data directories, for example local configuration or credential files readable by the process. Content disclosure to the attacker depends on how the application surfaces parser errors or results; file existence, size, and open behavior provide an oracle regardless. Because the same unvalidated path also skips the checks that refuse a non-regular file or an over-large model, the attacker can instead designate a FIFO, socket, device, or directory, or an oversized regular file, so the native load blocks or reads unbounded data into memory and the component performing the parse suffers reduced performance or a stalled worker. The primitive demonstrated by source analysis is attacker control of which file the process opens and reads.
Requires the optional bllipparser C extension to be installed
(pip install bllipparser) so that RerankingParser.load_parser_model
is available. No NLTK data download is needed to exercise the path.
# An application forwards an untrusted "model" path into BllipParser.
from nltk.parse.bllip import BllipParser
# Attacker-controlled value reaches the constructor unchanged.
untrusted_path = "/etc/passwd"
BllipParser(parser_model=untrusted_path)
# The bllipparser C extension opens and reads untrusted_path at the OS
# level with no NLTK pathsec confinement to the data roots.
Structural confirmation without the optional extension installed:
$ grep -n "pathsec\|validate_path\|validate_tool" nltk/parse/bllip.py
(no output)
$ grep -n "validate_tool_path" nltk/tag/crf.py nltk/tag/hunpos.py \
nltk/tag/stanford.py nltk/parse/malt.py
nltk/tag/crf.py:140
nltk/tag/crf.py:311
nltk/tag/hunpos.py:111
nltk/tag/stanford.py:85
nltk/tag/stanford.py:137
nltk/parse/malt.py:340
nltk/parse/malt.py:344
BllipParser is the only native-tool wrapper with no validate_tool_path call.
BllipParser (exported at nltk/parse/bllip.py:83 and re-exported at nltk/parse/__init__.py:47) and constructs it with a model path influenced by untrusted input.parser_model, reranker_features, or reranker_weights string (or the model_dir passed to from_unified_model_dir() at nltk/parse/bllip.py:218); it is accepted as an arbitrary string with no type or content constraint.BllipParser.__init__ (nltk/parse/bllip.py:125) to self.rrp.load_parser_model(parser_model, ...) at nltk/parse/bllip.py:164, and the reranker values to self.rrp.load_reranker_model(...) at nltk/parse/bllip.py:166-170.nltk/parse/bllip.py imports no pathsec symbol, so validate_tool_path() (the guard applied by every sibling wrapper) is never invoked before the native open.bllipparser C extension performs an OS-level open and read of the supplied path to parse model data, reaching the filesystem directly.The relevant protection is nltk.pathsec.validate_tool_path() (nltk/pathsec.py:950), documented as bounding "a filesystem path handed to an external tool to read or write" and designed exactly for native loaders that open the file themselves. The strongest counter-argument is that SECURITY.md disclaims being "a general operating-system sandbox" and does "not prevent all unsafe behavior an application might perform outside NLTK." That disclaimer concerns application code outside NLTK, not NLTK's own tool wrappers: the project added validate_tool_path() and applied it to CRFTagger.set_model_file() (nltk/tag/crf.py:140), HunposTagger (nltk/tag/hunpos.py:111), StanfordTagger (nltk/tag/stanford.py:85,137), and MaltParser (nltk/parse/malt.py:340,344), with the CRFTagger and HunposTagger wrappers citing a prior tool-path advisory and explaining that pathsec.open cannot be used because the C extension does its own open. BllipParser is the single native-tool wrapper that omits the guard. The alternative from_unified_model_dir() path offers no mitigation: get_unified_model_parameters() belongs to the third-party bllipparser package and performs no NLTK data-root validation, so the derived sub-paths reach the constructor and the C extension unchecked.
pipnltk>= 3.10.0, <= 3.10.33.10.3 (PyPI)The lower bound is 3.10.0 because the pathsec enforcement model and the validate_tool_path() guard were introduced in that release; before 3.10.0 no such boundary existed for NLTK to violate. The missing validation persists unchanged through 3.10.3, the current PyPI release, and on the repository default branch (no pathsec, validate_path, or validate_tool reference exists in nltk/parse/bllip.py). No fixed release is established.
Validate every caller-supplied native-tool path in BllipParser before it reaches the bllipparser C extension, matching the pattern already used by the other wrappers. In BllipParser.__init__, pass parser_model, reranker_features, and reranker_weights through nltk.pathsec.validate_tool_path() (with max_bytes=MAX_TOOL_MODEL_BYTES and require_private=True, as CRFTagger.set_model_file() does) and build the C-extension arguments from the returned strings so a path swapped after the check is not reopened. Because parser_model denotes a model directory for this loader, the fix must cover the directory argument and the reranker file arguments, and should apply equally to the paths derived inside from_unified_model_dir(). As an interim measure, applications should refuse to pass untrusted input as any BllipParser model path and should load models only through nltk.data.find(), which already enforces pathsec confinement.
Reported by zx.
EPSS, exploit probability
Low0.20%
estimated chance of real-world exploitation in the next 30 days, higher than 9.6% of every CVE FIRST.org scores
Refreshed 11/14/2026, via FIRST.org's EPSS model, not CVSS, this measures likelihood of exploitation, not how severe it would be.
More pip advisories
Talk to us about this
Find out whether this dependency (or others like it) is actually reachable in your codebase.
Get help patching, or building dependency-scanning into your pipeline going forward.
A secure code review or supply chain review finds this before an advisory does.