Repository navigation
Releases
v3.10.4
Version 3.10.4 2026-10-20
This release routes every file path, archive, download, URL fetch and external program through one sandbox that keeps reads and writes inside the trusted data directories, refuses path traversal, symlink escapes and decompression bombs, blocks requests to internal network addresses, and runs Java and other third-party tools only from trusted absolute locations with validated options and input. Pickled models now load through a restricted unpickler, XML and JSON parsing are hardened, and regular expressions, recursion and slow algorithms in the tokenizers, parsers, stemmers and corpus readers are bounded against denial-of-service input. Text that NLTK writes to the terminal, to CSV files, to the WordNet browser and to third-party tool input is sanitized against injection.
Security changes you may notice
Every refusal below raises with a message that starts with "Security Violation" and names the check, so a failure is easy to search for (PR #3877).
- Data directories (nltk.pathsec, nltk.data): corpora, models and every other resource are read and written only inside the directories listed in nltk.data.path. A path that leaves a data root, a symlink that points out of one, and a NUL byte or encoded traversal in a resource name are refused. A data root, or a directory on the way to a resource, that another account can write to is skipped with a warning naming the directory and the remedy. Keep private data in a private directory and add it to nltk.data.path; /tmp is not a data root. Model save and load methods (perceptron, maxent, named-entity chunker, Weka, crf, hunpos, Stanford) accept destinations inside a data root only (PR #3797, #3753, #3850, #3761, #3755, #3759, #3760, #3763, #3806, #3807, #3808, #3809, #3813, #3814, #3815, #3848, #3851, #3852, #3853, #3863, #3871, #3888, #3945, #3961, #3969, #3981)
- Downloader: downloaded package archives stay private to the installing account (0600). An install another account must read is extracted: a download made as root or outside the installing account's home extracts on its own, and nltk.download(..., extract=True) / python -m nltk.downloader --extract (or --no-extract) decide explicitly. This also unpacks the packages the index leaves zipped (wordnet, omw-1.4 ...); the extracted files are readable by every account and are found before the archive. find() names an archive it cannot read and says how to extract it instead of reporting it "not found". The downloader installs only into directories no other account can write to, treats an archive another account owns or can write to as stale and downloads it afresh, extracts only from an archive no other account could have changed since its checksum was verified, bounds the size of what it fetches, and refuses index entries and archive members that would write outside the package directory (PR #3929, #3804, #3850, #3778)
- Pickles: nltk.data.load(..., format="pickle") reconstructs plain data only; a pickle that names any class or function is refused. Model loaders that need classes use an explicit allowlist of audited globals, so a gadget such as os.system can never be reconstructed from a model file. chat80 shelve values load the same way (PR #3794, #3754, #3757, #3802, #3817, #3823, #3824)
- XML, JSON and YAML: nltk.xmlsec refuses any document that declares an entity or references an external one (billion-laughs, external-entity and DTD attacks) and bounds the tree depth, and every corpus XML view, the downloader index, Toolbox and internals parse through it. JSON resources load with a depth and size bound; YAML (the markdown corpus front matter and nltk.data.load(..., format="yaml")) loads through a bounded safe loader. Oversized or too deeply nested input raises ValueError naming the limit (PR #3754, #3805, #3828, #3933, #3972, #3974)
- Regular expressions (nltk.redos): every regular expression NLTK compiles, including one you pass to RegexpTokenizer, RegexpStemmer, RegexpParser, re_show, TokenSearcher.findall, tgrep, nltk.help tag-set lookups and the concordance and nemo GUIs, now goes through nltk.redos on the regex engine. A pattern longer than 100,000 characters, nested more than 100 deep, with more than 1,000 capturing groups or an excessive repetition product is refused with nltk.redos.error, a subclass of re.error; a match that runs longer than nltk.redos.DEFAULT_TIMEOUT (5 seconds) raises TimeoutError instead of hanging. Patterns keep re syntax; match objects are the regex engine's (PR #3825, #3829, #3830, #3831, #3832, #3833, #3834, #3835, #3836, #3837, #3838, #3839, #3840, #3841, #3842, #3843, #3844, #3845, #3846, #3796, #3801, #3906, #3907, #3908, #3910)
- Regular expressions made linear: the leading-run and re-anchoring patterns that a timeout cannot catch were rewritten or bounded in the word, Treebank, blankline, TextTiling, toktok and syllable tokenizers, the readline of corpus streams, the CoNLL SRL, TIMIT, senseval, pl196x, NKJP, Alpino, YCOE, IEER, ACE, VerbNet, reviews and Lin readers, relextract, the valuation parser, the CCG lexicon parser, dependency grammars and chomsky_normal_form, each verified on the real corpus (PR #3947, #3934, #3932, #3938, #3935, #3933, #3869, #3944, #3911, #3909, #3953, #3895)
- Size, depth and time bounds: trees deeper than nltk.tree.MAX_TREE_DEPTH (500), WordNet and other relation walks deeper than nltk.util.MAX_RECURSION_DEPTH (500), tgrep deeper than 50, Toolbox deeper than 500, dependency graphs deeper than 200, are refused with ValueError naming the limit rather than exhausting the stack, and the stemmers, distance metrics, skipgrams, parsers and grammar transforms run in linear or bounded time with identical output for legitimate input. chart_parse stops after nltk.parse.chart.DEFAULT_MAX_TIME (5 seconds of wall-clock, needed only for feature grammars; max_time=None disables it). Tree.repr no longer recurses, so a deep accepted tree prints (PR #3827, #3885, #3895, #3767, #3784, #3917, #3958, #3960, #3977)
- External tools and Java: a tool binary is resolved from an absolute path or a trusted search path only, never from the current directory; the binary, its directory and the model and data files handed to it must not be writable by another account (symlinked and multi-call executables such as a packaged Graphviz dot are supported). java() accepts a main class as the first argument only (no -jar, @argfile or .java source file), validates its options against an allowlist, and loads jars only from a data root unless NLTK_ALLOW_UNSAFE_JARS=1 opts out with a warning. CoreNLP server options are allowlisted. Stanford, Malt, Senna, hunpos, Prover9, megam, TADM, Weka and crf wrappers validate their paths and the text they pipe to the tool against line and argument injection (PR #3772, #3771, #3975, #3743, #3782, #3788, #3768, #3798, #3887, #3858, #3950, #3951, #3812, #3816, #3868, #3894, #3941, #3850, #3925)
- Network: the downloader, nltk.data.load of a URL and the CHILDES reader fetch through pathsec.urlopen, which refuses addresses that are not globally routable (loopback, private, link-local and multicast, including tunnelled and obfuscated forms), pins the validated address for the connection, allows file:// with a local authority only, and refuses a proxied fetch unless NLTK_ALLOW_PROXIED_URLOPEN=1 (or nltk.pathsec.ALLOW_PROXIED_FETCH = True) asserts the proxy is trusted. The CoreNLP client talks to its local server with requests and is not affected (PR #3847, #3810, #3750, #3850)
- Terminal, CSV, DOT and HTML output: every print in the library goes through nltk.termsec.safe_print, which renders control characters, escape sequences and unbalanced bidi overrides from corpus text as visible \xNN escapes instead of letting them reach the terminal. The CSV files the twitter and sentiment helpers write neutralise formula-leading and control-sequence cells, AlignedSent DOT labels are escaped, the WordNet browser escapes corpus text, and nltk.data.load's verbose output no longer prints the full resource URL (PR #3914, #3915, #3937, #3946, #3931, #3930, #3856, #3902)
- Corpus stores and encodings: the PanLex Lite and chat80 sqlite stores open read-only on a pinned descriptor with their schema screened and a statement step budget, chat80 shelves are read by a key manifest, Toolbox SFM markers are sanitised, and a resource encoding that is not a text codec (bz2_codec and friends) is refused (PR #3961, #3964, #3959, #3888, #3800, #3962)
- Package contents: the wheel no longer ships nltk.test.unit (the attack harnesses, advisory probes and their fixtures), so pip install nltk is about half the size and import nltk.test.unit works from a source checkout or the sdist only. The doctests under nltk.test still ship (PR #3954)
Security test and CI infrastructure
- Attack and regression harnesses, advisory probes, the sandbox conftest fixtures, the pre-commit guards (no unsandboxed open, every regex through redos, every pickle through picklesec, every print through safe_print), load-invariant CPU-time timing rules, CI matrix and runner fixes, pinned third-party downloads, and the AUTHORS update (PR #3744, #3749, #3762, #3764, #3770, #3776, #3791, #3795, #3803, #3818, #3820, #3821, #3822, #3849, #3855, #3860, #3861, #3862, #3864, #3865, #3866, #3876, #3884, #3899, #3904, #3905, #3912, #3917, #3924, #3926, #3927, #3939, #3940, #3943, #3948, #3949, #3963, #3968, #3971, #3976, #3982, #3983, #3984, #3985)
Other changes
- Added RTL language syntactic tree visualization support (PR #3472)
- Fix #3394: Ensure punkt_tab is downloaded when punkt is requested (PR #3505)
- Show README.md as the PyPI long description (PR #3746)
- Add stopwords.words("english-extended") (PR #3747)
- Krippendorff's alpha: treat placeholder labels as missing data (#2865, #2732) (PR #3752)
- Fix shared defaults in chat80 Concept (PR #3773)
- Fix Java wrappers decoding str output from java() (universal_newlines) (PR #3777)
- Prevent IndexError in Hungarian stemmer on empty ...
v3.10.4-rc1
Version 3.10.4 (unreleased)
This release routes every file path, archive, download, URL fetch and external program through one sandbox that keeps reads and writes inside the trusted data directories, refuses path traversal, symlink escapes and decompression bombs, blocks requests to internal network addresses, and runs Java and other third-party tools only from trusted absolute locations with validated options and input. Pickled models now load through a restricted unpickler, XML and JSON parsing are hardened, and regular expressions, recursion and slow algorithms in the tokenizers, parsers, stemmers and corpus readers are bounded against denial-of-service input. Text that NLTK writes to the terminal, to CSV files, to the WordNet browser and to third-party tool input is sanitized against injection.
- Downloaded package archives stay private to the installing account (0600). An install another account must read is extracted: a download made as root or outside the installing account's home extracts on its own, and
nltk.download(..., extract=True)/python -m nltk.downloader --extract(or--no-extract) decide explicitly. This also unpacks the packages the index leaves zipped (wordnet, omw-1.4 ...); the extracted files are readable by every account and are found before the archive, andnltk.download()called by that account reports the package installed.find()now names an archive it cannot read and says how to extract it instead of reporting it "not found" (#3928) nltk.data.find()refuses a data root, or a directory on the way to a resource, that another account can write to: a file planted there would be loaded as trusted data. It warns, names the directory and the remedy, and keeps searching later roots. A corpus root is judged with everything under it, and an archive is judged again on the descriptor at every read- The downloader installs only into directories no other account can write to, creates them without a group or world write bit whatever the umask, treats an archive another account owns or can write to as stale and downloads it afresh, and extracts only from an archive no other account could have changed since its checksum was verified
- Create perceptron model files exclusively under the pinned directory (issue 3980) (PR #3981)
- Make Tree.repr iterative so a deep accepted tree does not overflow the stack (PR #3977)
- Guard java() cmd[0] against JEP 330 source-file execution, verified on a real JDK (PR #3975)
- Bound YAML deserialization against depth and resource DoS, verified on the real loaders (PR #3974)
- Fix DependencyGraph cycle detection, dead on every parsed graph (PR #3978)
- Open gzip file names through pathsec, and refuse a linked PanLex Lite database (PR #3961)
- Route the downloader, XML corpus view, internals and Toolbox through nltk.xmlsec and nltk.data (PR #3972)
- Harden the Toolbox reader and writers after the #3958 re-audit (PR #3962)
- Refuse relation walks deeper than MAX_RECURSION_DEPTH instead of truncating them (PR #3960)
- Fix the develop CI breakage from the fg9w advisory merge (Senna -path separator, test fixtures under /tmp) (PR #3971)
- Route every module's file removal, rename, privacy test and sqlite open through pathsec (PR #3969)
- Close the chat80 store swap window and read stores by a key manifest (PR #3964)
- CI: take the Windows Graphviz from the cached third-party tools, not Chocolatey (PR #3968)
- Keep full collections out of timed scaling samples (89p3 teeth and relextract reds) (PR #3963)
- Let chat80.val_load read back what val_dump wrote (PR #3959)
- Restore add_blank_lines and to_settings_string after the GHSA-f2h2 depth bound (PR #3958)
- Fix #3394: Ensure punkt_tab is downloaded when punkt is requested (PR #3505)
- Guard the two-class any-character runs and count apostrophe-named groups (follow-up to #3895) (PR #3953)
- Bound algorithmic-DoS and uncontrolled-recursion vectors (CWE-400/407/674/1333) (PR #3895)
- [Do not merge yet] Keep downloaded archives private and serve other accounts through extraction (fixes #3928) (PR #3929)
- Support symlinked executables in pathsec (PR #3951)
- Trusted spawn: O_NOFOLLOW and inode check on the final file, link owner and control-character checks, refusal reasons, argv[0] as invoked; real Graphviz in CI (#3887 follow-up) (PR #3950)
- Pair the scaling samples by rep and size the under-margined probes over the floor (PR #3949)
- Add the 3.10.4 commit authors and security reporters to AUTHORS.md (PR #3948)
- Make the word, blankline and TextTiling tokenizers, the valuation parser, relextract and toktok linear on retried leading regex runs (PR #3947)
- Added RTL language syntactic tree visualization support (PR #3472)
- [Do not merge yet] Guard tool wrappers, Prover9 input and the downloader against injection and unbounded-response DoS (PR #3850)
- Parse CCG lexicon categories in linear time and refuse oversized ones, verified under attack and on the documented grammars (GHSA-89p3-fcch-88ph, split from #3753) (PR #3944)
- Guard the maxent parameter files against outside-root paths and unrepresentable names, verified on a real trained model (PR #3945)
- Guard the twitter CSV exports against formula and control-sequence injection, verified on the real corpus (PR #3946)
- Bring the self-contained tests of #3753 to develop (PR #3940)
- Guard the Stanford tagger and segmenter input against line injection, verified on the real tools (PR #3941)
- Surface the CoreNLP server's reason for a non-2xx answer and warm the live test server (PR #3942)
- ci(deps): bump dorny/paths-filter from 3 to 4 in the github-actions group (PR #3943)
- Measure probe scaling in process CPU time so a loaded runner cannot flip a verdict (PR #3939)
- Make the CoNLL SRL, TIMIT and XML view readers linear, and parse corpus XML through xmlsec (GHSA-v8f3) (PR #3933)
- Fix UTF-8 text preprocessing in NKJP corpus reader (PR #3879)
- Walk semi-relations once in relextract, and keep a pattern's flags in redos.reharden (PR #3935)
- Find readline's line boundary with str.splitlines, not a timed regex (PR #3938)
- Never leave the pop of an escaped bidi override live in sanitize_terminal (PR #3937)
- Align quote tokens in linear time in the Treebank and NLTK word tokenizers (PR #3934)
- Escape AlignedSent DOT labels, refuse NUL, and keep the alignment checks under -O (PR #3930)
- Read a long line in linear time in SeekableUnicodeStreamReader.readline (GHSA-j8g8) (PR #3932)
- Sanitise the CSV cells and report lines the sentiment helpers write (PR #3931)
- Consolidate the nltk/parse guards from #3753 and #3850 and judge their inputs on real characters (PR #3927)
- Consolidate the internals, downloader, pathsec and decorators guards from #3753 and #3850 (PR #3926)
- Extend the tool model-file guards to crf, stanford tagger and segmenter (PR #3871)
- Validate chat80.sql_query dbname for path containment (CWE-59/73) (PR #3888)
- Pin the GHSA-cc5r-64rf-75hg proof of concept end to end and run its sink as probe phase 6 (follow-up to #3887) (PR #3925)
- Log the perceptron training progress on the module logger (fixes the develop 3.10/3.11 cells) (PR #3924)
- Route every library print through the nltk.termsec.safe_print chokepoint (PR #3915)
- Fix the XMLCorpusView ReDoS-test Windows CI flake (in-process op timing + Windows autoload) (PR #3912)
- Fix develop CI on Python 3.10/3.11: let deepcopy reach MAX_TREE_DEPTH (PR #3917)
- Bound SyllableTokenizer fallback-vowel accumulation (CWE-400 / CWE-407) (PR #3911)
- [WIP] Fix quadratic DoS in Tree.chomsky_normal_form right-factor binarization (GHSA-r53h-rw34-8h97) (PR #3909)
- Add nltk.termsec terminal-output sanitizer with attack-matrix tests (PR #3914)
- Make help._format_tagset fail closed on an oversized tag pattern (fix develop CI) (PR #3910)
- Handle the redos MAX_PATTERN_LENGTH refusal for a tgrep /regex/ node literal (PR #3908)
- Handle the redos MAX_PATTERN_LENGTH refusal in TokenSearcher.findall (PR #3907)
- Handle the redos MAX_PATTERN_LENGTH refusal in help._format_tagset (PR #3906)
- Add GHSA-j456 live probe; expand the GHSA-8mgp probe attack set (PR #3905)
- Fix resolution paths that cancel distinct literals (PR #3878)
- Remove shadowed FeatStruct._walk stub and preserve its docstring (PR #3893)
- Potential fix for code scanning alert no. 28: Clear-text logging of sensitive information (PR #3902)
- Count all increasing pairs in RIBES kendall_tau (PR #3900)
- Extend no-unsandboxed-open guard to gzip/bz2/lzma/zipfile/tempfile (+ coverage test) (PR #3904)
- Make DoS/ReDoS timing regression tests load-invariant (fix CI flakiness) (PR #3899)
- Harden CoreNLPServer start attempts (PR #3894)
- Harden and complete the CoreNLP server-option allowlist (CWE-88/22/502) (PR #3868)
- Bound recursion depth in parsers and traversal helpers (PR #3885)
- Give every pathsec refusal the standard Security Violation marker (spawn_trusted + ZIP audit) (PR #3877)
- Don't miscount a python-crfsuite Windows failure as a pathsec over-block (tag sweep) (PR #3876)
- Harden and standardize NLTK's external-tool wrappers on a pathsec trusted-spawn API (CWE-426/427/732/93/88) (PR #3858)
- Deflake anti-DoS timing tests and harden senseval _fixXML leading-run ReDoS (PR #3869)
- Tidy transitionparser security comments to one point per <=3 lines (PR #3866)
- Bound the WekaClassifier model path to the pathsec data roots (PR #3863)
- Open operator-chosen app/twitter paths with an annotated builtin open, not pathsec.open (PR #3865)
- Exclude pytest's ephemeral basetemp from the pickle-asset scanner (PR #3864)
- Add the all-regex-through-redos pre-commit guard and two regression tests (PR #3862)
- Extend the no-unsandboxed-open guard to app/draw/twitter and route every open through pathsec.open (PR #3861)
- Add test-only regression coverage for already-fixed CWEs (PR #3860)
- Escape corpus-derived text in the WordNet browser app (CWE-79 XSS) (PR #3856)
- Add and expand security / ReDoS attack test coverage (PR #3855)
- Re-check th...
v3.10.3-rc1
Version 3.10.3 2026-08-12
- docs: wrap Chat-80 HOWTO output
- Sandbox Stanford JAR execution to nltk_data directories
- Harden path-traversal / file-I/O sandbox: close write-side symlink TOCTOU + shared-temp squat, lock the cluster with a living audit (CWE-22/59/377)
- Extend algorithmic-complexity DoS hardening: repo-wide sweep + two-string distances (CWE-407/CWE-400)
- Bound unbounded-work DoS in parsers and grammar transforms (CWE-407/674/835)
- fix(security): sandbox MaltParser's Java execution (CVE-2026-12252, CVE-2026-12841)
- fix(security): trust the system temp dir only when it is private (CWE-377/CWE-378)
- fix(security): validate corpus-reader roots against the data sandbox (CWE-73)
- fix(security): validate per-call java() options and replace the -XX:/-D allowlist with a minimal one (CWE-88)
- Additional security hardening (CWE-407, CWE-426, CWE-427, CWE-502, CWE-59, CWE-776, CWE-918)
Thanks to the following contributors to 3.10.3: Mohammad Favas S, leduckhuong, Ziyu Lin, dougtrainer28-cmyk, Chaitanya Kadian, 0xRenSec, Arpit Jain, Jace, nguyencanhthuong, Liling Tan, medimedi, Eric Kafe.
What's Changed
- Sandbox Stanford JAR execution to nltk_data directories by @ekaf in #3743
- docs: wrap Chat-80 HOWTO output by @medisean in #3741
- ci: sandboxed-open guard + stabilize security tests across platforms/pythons (#3740) by @alvations in #3744
- Prepare release 3.10.3 by @ekaf in #3745
New Contributors
Full Changelog: v3.10.2...v3.10.3-rc1
v3.10.3
Version 3.10.3 2026-08-12
- docs: wrap Chat-80 HOWTO output
- Sandbox Stanford JAR execution to nltk_data directories
- Harden path-traversal / file-I/O sandbox: close write-side symlink TOCTOU + shared-temp squat, lock the cluster with a living audit (CWE-22/59/377)
- Extend algorithmic-complexity DoS hardening: repo-wide sweep + two-string distances (CWE-407/CWE-400)
- Bound unbounded-work DoS in parsers and grammar transforms (CWE-407/674/835)
- fix(security): sandbox MaltParser's Java execution (CVE-2026-12252, CVE-2026-12841)
- fix(security): trust the system temp dir only when it is private (CWE-377/CWE-378)
- fix(security): validate corpus-reader roots against the data sandbox (CWE-73)
- fix(security): validate per-call java() options and replace the -XX:/-D allowlist with a minimal one (CWE-88)
- Additional security hardening (CWE-407, CWE-426, CWE-427, CWE-502, CWE-59, CWE-776, CWE-918)
Thanks to the following contributors to 3.10.3: Mohammad Favas S, leduckhuong, Ziyu Lin, dougtrainer28-cmyk, Chaitanya Kadian, 0xRenSec, Arpit Jain, Jace, nguyencanhthuong, Liling Tan, medimedi, Eric Kafe.
What's Changed
- Sandbox Stanford JAR execution to nltk_data directories by @ekaf in #3743
- docs: wrap Chat-80 HOWTO output by @medisean in #3741
- ci: sandboxed-open guard + stabilize security tests across platforms/pythons (#3740) by @alvations in #3744
- Prepare release 3.10.3 by @ekaf in #3745
New Contributors
Full Changelog: v3.10.2...v3.10.3
v3.10.2-rc1
Version 3.10.2 2026-08-05
- Remove inisec.py and document PYTHONSAFEPATH instead
- Skip draft step in release workflow
- Fix symlink escape in FramenetCorpusReader (CWE-59)
- Guard tempfile.gettempdir() when building pathsec allowed roots
- add tests for transitive_closure
Thanks to the following contributors to 3.10.2:
Litesh Ghute, Eric Kafe, Evan Kiefer, tarann26 and Rav Singh Chandan
v3.10.2
Version 3.10.2 2026-08-05
- Remove inisec.py and document PYTHONSAFEPATH instead
- Skip draft step in release workflow
- Fix symlink escape in FramenetCorpusReader (CWE-59)
- Guard tempfile.gettempdir() when building pathsec allowed roots
- add tests for transitive_closure
Thanks to the following contributors to 3.10.2:
Litesh Ghute, Eric Kafe, Evan Kiefer, tarann26 and Rav Singh Chandan
v3.10.1-rc2
What's Changed
- use exact thirds in masi_distance (fixes #3361) by @CodingSelim in #3695
- Fix uncontrolled search path in HunposTagger by @ekaf in #3715
- Fix word_tokenize: pad opening single quote before multi-letter words by @trinik15 in #3687
- Avoid retaining bllip import exceptions by @Muhtasim-Munif-Fahim in #3601
- Implement Tree.pformat_latex_forest by @tyomitch in #2956
- fix(security): validate types after wordnet_app pickle deserialization by @LiteshGhute in #3714
- Expand ~ in env-var paths (NLTK_DATA, find_file_iter) by @Abhi-DevHub in #3708
- Update ChangeLog for v. 3.10.1 by @ekaf in #3717
New Contributors
- @trinik15 made their first contribution in #3687
- @tyomitch made their first contribution in #2956
- @Abhi-DevHub made their first contribution in #3708
Full Changelog: v3.10.1-rc1...v3.10.1-rc2
v3.10.1-rc1
What's Changed
- Fix perfect-agreement edge cases in agreement metrics by @Muhtasim-Munif-Fahim in #3608
- fix(security): prevent ReDoS in TextTilingTokenizer paragraph-break regex (CWE-1333) by @LinZiyuu in #3612
- fix(security): prevent ReDoS in SensevalCorpusReader _fixXML (CWE-400) by @LinZiyuu in #3614
- fix(security): resolve relative MaltParser dir via MALT_PARSER, not the CWD (CWE-426) by @LinZiyuu in #3616
- fix(security): bound LogicParser recursion depth to prevent uncaught RecursionError (CWE-674) by @LinZiyuu in #3617
- fix(security): resolve relative ReppTokenizer dir via REPP_TOKENIZER, not the CWD (CWE-427) by @LinZiyuu in #3618
- fix(security): do not load weka.jar from the CWD (CWE-494) by @LinZiyuu in #3620
- fix(security): do not run the senna binary from the CWD (CWE-829) by @LinZiyuu in #3621
- fix(security): prevent ReDoS in chunk tag-pattern parsing (CWE-1333) by @LiteshGhute in #3630
- Route _download_package() writes through pathsec.open by @ekaf in #3622
- fix(security): resolve symlinks in downloader containment check (CWE-59) by @LinZiyuu in #3619
- fix(security): use defusedxml in corpus readers to block XML entity-expansion DoS (CWE-776) by @LinZiyuu in #3615
- fix(security): stop exponential ReDoS in XMLCorpusView _VALID_XML_RE (CWE-1333) by @LinZiyuu in #3646
- fix(security): route read_sents through pathsec sentinel by @ekaf in #3626
- Add RELEASE-Github.md documentation by @ekaf in #3627
- fix(security): do not run the candc/boxer binary from the CWD (CWE-426) by @LinZiyuu in #3623
- fix(security): algorithmic-complexity DoS in DependencyGraph.contains_cycle by @LinZiyuu in #3632
- fix(security): bound recursion in PorterStemmer._is_consonant (CWE-674) by @LinZiyuu in #3633
- fix(security): close validate/extract path mismatch in pathsec Zip-Slip guard (CWE-22) by @LinZiyuu in #3634
- fix(security): validate path in GzipFileSystemPathPointer.open() (CWE-22) by @LinZiyuu in #3635
- fix(security): require a session token for WordNet Browser shutdown (CWE-352) by @LinZiyuu in #3637
- fix(security): remove quadratic scan in VADER polarity_scores (CWE-407) by @LinZiyuu in #3638
- fix(security): bound super-linear n-gram scan in RIBES word_rank_alignment (CWE-407) by @LinZiyuu in #3639
- fix(security): route custom_distance through the pathsec sentinel (CWE-22) by @LinZiyuu in #3640
- fix(security): stop quadratic ReDoS in CCG lexicon parsing (CWE-1333) by @LinZiyuu in #3641
- fix(security): reject CWD-relative binaries in find_binary (CWE-426) by @LinZiyuu in #3642
- fix(ci): resolve isort block (#3615) and restore CCG lexicon syntax (#3641) by @LinZiyuu in #3677
- fix(security): read XML corpora through the path-pointer sentinel (CWE-22) by @LinZiyuu in #3636
- fix(security): cap zip decompression to prevent decompression-bomb DoS (CWE-409) by @LinZiyuu in #3613
- fix(security): stop symlink-following arbitrary deletion in Downloader stale-cleanup (CWE-59) by @LinZiyuu in #3643
- fix(security): sparse Alignment index to stop unbounded-allocation DoS (CVE-2026-12837) by @LinZiyuu in #3651
- fix(security): sparse ConfusionMatrix to stop quadratic-memory DoS (CVE-2026-12839) by @LinZiyuu in #3652
- fix(security): bound Model.satisfy quantifier-nesting cost to stop combinatorial DoS (CVE-2026-12840) by @LinZiyuu in #3653
- fix(security): bound everygrams default max_len to stop unbounded-allocation DoS (CVE-2026-12861) by @LinZiyuu in #3655
- fix(security): route DependencyGraph.load through the pathsec sandbox (CVE-2026-12867) by @LinZiyuu in #3656
- fix(security): make Cistem stemmer linear to stop quadratic-time DoS (CVE-2026-12868) by @LinZiyuu in #3658
- fix(security): route data.retrieve write through the pathsec sandbox (CVE-2026-12871) by @LinZiyuu in #3659
- fix(security): bound DRT resolve_anaphora work to stop quadratic time+memory DoS (CVE-2026-12873) by @LinZiyuu in #3660
- fix(security): bound chart parse-tree extraction to stop exponential DoS (CVE-2026-12886) by @LinZiyuu in #3661
- fix(security): make valuation separator regex linear to stop quadratic ReDoS (CVE-2026-12890) by @LinZiyuu in #3662
- fix(security): make feature-structure rename regex linear to stop quadratic ReDoS (CVE-2026-12919) by @LinZiyuu in #3663
- fix: enforce package ownership during ZIP extraction by @ibfavas in #3625
- fix(security): block IPv6 transition-embedded internal IPv4 in SSRF filter (NAT64 bypass, CWE-918) by @LinZiyuu in #3644
- fix(security): bound generate() derivation-expansion steps to stop unbounded-enumeration DoS (CVE-2026-12864) by @LinZiyuu in #3654
- fix(security): bound phrase_extraction default max_phrase_length to stop cubic DoS (CVE-2026-12870) by @LinZiyuu in #3657
- Prevent Arbitrary File Write via XML Attribute Injection in nltk.downloader by @ekaf in #3628
- Cache CRFTagger feature extraction; harden boundaries and add tests by @jhnwnstd in #3573
- Fix duplicated -l short option on
nltk tokenize(#3342) by @jbbqqf in #3580 - fix(security): make jaro_similarity flagged-position lookup O(1) to stop cubic DoS (CVE-2026-12926) by @LinZiyuu in #3664
- fix(security): bound NgramCounter distinct-ngram count to stop unbounded-memory DoS (CVE-2026-12928) by @LinZiyuu in #3665
- fix(security): make METEOR word matching linear to stop quadratic-time DoS (CVE-2026-12929) by @LinZiyuu in #3666
- fix(security): bound theorem-prover proof search to stop unbounded-search DoS (CWE-400 / CWE-674) by @LinZiyuu in #3667
- fix(security): guard CHILDESCorpusReader.convert_age against malformed input (CWE-476) by @LinZiyuu in #3668
- fix(security): validate CoNLL / CMUdict reader lines to stop malformed-line crash (CWE-20) by @LinZiyuu in #3669
- fix(security): quadratic DoS in NLTK LEPOR alignment by @LinZiyuu in #3670
- fix(security): DoS in NLTK PropBank by @LinZiyuu in #3671
- fix(security): Denial of service via quadratic complexity in NLTK segmentation metrics by @LinZiyuu in #3672
- fix(security): Quadratic-time DoS in NLTK Tree.un_chomsky_normal_form by @LinZiyuu in #3673
- fix(security): Quadratic ReDoS in NLTK Text.findall token search by @LinZiyuu in #3674
- fix(security): Exponential beta-reduction DoS in NLTK Expression.simplify by @LinZiyuu in #3675
- fix(security): Quadratic-memory DoS in NLTK ProjectiveDependencyParser by @LinZiyuu in #3676
- fix(security): DoS in NLTK NomBank instance parsing by @LinZiyuu in #3678
- fix(security): DoS in NLTK segmentation pk metric via Uncaught divide-by-zero by @LinZiyuu in #3679
- fix(security): Quadratic-backtracking ReDoS in NLTK AlpinoCorpusReader._normalize by @LinZiyuu in #3684
- fix(security): Quadratic-time DoS in NLTK Markdown corpus reader blockquotes/lists by @LinZiyuu in #3685
- fix(security): Quadratic-time DoS in NLTK GDFA by @LinZiyuu in #3686
- Use OMW-2.0 with all WordNet versions by @ekaf in #3690
- Fix CWE-88 JVM argument injection in java_options (CVE-2026-12841) by @samrigby64 in #3647
- fix(security): MaltParser process-wide CWD pollution during parsing (CVE-2026-12614) by @haoami in #3682
- fix(security): prevent pickle RCE in TransitionParser model loading (CWE-502) by @LiteshGhute in #3631
- ci(deps): bump the github-actions group with 3 updates by @dependabot[bot] in #3691
- fix(security): clean Stanford Java wrapper temp files and isolate Java options (CVE-2026-12615) by @haoami in #3683
- Enforce restrictive primitive type checking in pathsec wrappers by @ekaf in #3692
- Fix EOF handling in IEERCorpusReader to prevent empty document yields by @CHENNA-NAGESH in http...
v3.10.0
What's Changed
- Let TextCat.guess_language return None for ties by @ekaf in #3543
- Fix FunctionalCategory.substitute() ignoring substituted direction by @elias-ba in #3547
- Fix concurrent download race conditions with file-based locking by @ekaf in #3549
- Fix proxy settings ignored by pathsec.urlopen by @ekaf in #3552
- Fix CCG variable direction parsing pipeline by @ekaf in #3556
- Fix failing CCG unit tests by @ekaf in #3561
- Fix CCG variable capture, chart explosion, and type-raising scope bugs by @ekaf in #3557
- fix(security): block XML entity expansion (XXE) in downloader by @scruge1 in #3544
- Fix _create_token_table skipping last paragraph break transition + regression test by @13rac1 in #3553
- ci(deps): bump actions/github-script from 7 to 8 in the github-actions group by @dependabot[bot] in #3546
- Add HuggingFace datasets integration (nltk.huggingface) by @alvations in #3569
- fix PorterStemmer not using lowercased word in stem() for irregular forms in NLTK mode by @Lemm1 in #3568
- ci(deps): bump the github-actions group with 2 updates by @dependabot[bot] in #3572
- Fix WordNetObject comparisons for incompatible types by @ekaf in #3570
- Align TnT with Brants (2000) specifications by @jhnwnstd in #3564
- Avoid KeyError in langnames.py by @ekaf in #3404
- Fix TransitionParser sparse index dtype for scikit-learn 1.9 by @ekaf in #3591
- Enable ENFORCE by default and harden downloader concurrency by @ekaf in #3593
- Use commonpath for downloader path containment on Windows by @ekaf in #3594
- Use lexical containment for downloader target paths by @ekaf in #3595
- fix(security): prevent DNS-rebinding SSRF in pathsec.urlopen by @LinZiyuu in #3582
- fix(security): block URL-encoded path traversal in nltk: resource URLs by @LinZiyuu in #3575
- fix(security): block path traversal in NKJPCorpusReader by @LinZiyuu in #3579
- fix(security): block path traversal in FramenetCorpusReader.frame() by @LinZiyuu in #3581
- fix(security): prevent ReDoS in ReviewsCorpusReader FEATURES regex (CWE-1333) by @LinZiyuu in #3583
- Fix remaining corpus reader pathsec gaps before ENFORCE=True RC by @ekaf in #3588
- Cache WordNet max depth lazily for lch_similarity() by @ekaf in #3592
- Fix remaining post-merge issues from #3477 and #3498 by @ekaf in #3599
- Replace unsafe exec() with importlib in utility CLI by @ekaf in #3609
- Document NLTK's new security policy by @ekaf in #3597
- Check that CI succeeded on the commit a release tag points to by @ekaf in #3506
- Prepare release metadata for 3.10.0 by @ekaf in #3610
- Fix release workflow CI check for tagged commits by @ekaf in #3611
- Fix perfect-agreement edge cases in agreement metrics by @Muhtasim-Munif-Fahim in #3608
- fix(security): prevent ReDoS in TextTilingTokenizer paragraph-break regex (CWE-1333) by @LinZiyuu in #3612
- fix(security): prevent ReDoS in SensevalCorpusReader _fixXML (CWE-400) by @LinZiyuu in #3614
- fix(security): resolve relative MaltParser dir via MALT_PARSER, not the CWD (CWE-426) by @LinZiyuu in #3616
- fix(security): bound LogicParser recursion depth to prevent uncaught RecursionError (CWE-674) by @LinZiyuu in #3617
- fix(security): resolve relative ReppTokenizer dir via REPP_TOKENIZER, not the CWD (CWE-427) by @LinZiyuu in #3618
- fix(security): do not load weka.jar from the CWD (CWE-494) by @LinZiyuu in #3620
- fix(security): do not run the senna binary from the CWD (CWE-829) by @LinZiyuu in #3621
- fix(security): prevent ReDoS in chunk tag-pattern parsing (CWE-1333) by @LiteshGhute in #3630
- Route _download_package() writes through pathsec.open by @ekaf in #3622
- fix(security): resolve symlinks in downloader containment check (CWE-59) by @LinZiyuu in #3619
- fix(security): use defusedxml in corpus readers to block XML entity-expansion DoS (CWE-776) by @LinZiyuu in #3615
- fix(security): stop exponential ReDoS in XMLCorpusView _VALID_XML_RE (CWE-1333) by @LinZiyuu in #3646
- fix(security): route read_sents through pathsec sentinel by @ekaf in #3626
- Add RELEASE-Github.md documentation by @ekaf in #3627
- fix(security): do not run the candc/boxer binary from the CWD (CWE-426) by @LinZiyuu in #3623
- fix(security): algorithmic-complexity DoS in DependencyGraph.contains_cycle by @LinZiyuu in #3632
- fix(security): bound recursion in PorterStemmer._is_consonant (CWE-674) by @LinZiyuu in #3633
- fix(security): close validate/extract path mismatch in pathsec Zip-Slip guard (CWE-22) by @LinZiyuu in #3634
- fix(security): validate path in GzipFileSystemPathPointer.open() (CWE-22) by @LinZiyuu in #3635
- fix(security): require a session token for WordNet Browser shutdown (CWE-352) by @LinZiyuu in #3637
- fix(security): remove quadratic scan in VADER polarity_scores (CWE-407) by @LinZiyuu in #3638
- fix(security): bound super-linear n-gram scan in RIBES word_rank_alignment (CWE-407) by @LinZiyuu in #3639
- fix(security): route custom_distance through the pathsec sentinel (CWE-22) by @LinZiyuu in #3640
- fix(security): stop quadratic ReDoS in CCG lexicon parsing (CWE-1333) by @LinZiyuu in #3641
- fix(security): reject CWD-relative binaries in find_binary (CWE-426) by @LinZiyuu in #3642
- fix(ci): resolve isort block (#3615) and restore CCG lexicon syntax (#3641) by @LinZiyuu in #3677
- fix(security): read XML corpora through the path-pointer sentinel (CWE-22) by @LinZiyuu in #3636
- fix(security): cap zip decompression to prevent decompression-bomb DoS (CWE-409) by @LinZiyuu in #3613
- fix(security): stop symlink-following arbitrary deletion in Downloader stale-cleanup (CWE-59) by @LinZiyuu in #3643
- fix(security): sparse Alignment index to stop unbounded-allocation DoS (CVE-2026-12837) by @LinZiyuu in #3651
- fix(security): sparse ConfusionMatrix to stop quadratic-memory DoS (CVE-2026-12839) by @LinZiyuu in #3652
- fix(security): bound Model.satisfy quantifier-nesting cost to stop combinatorial DoS (CVE-2026-12840) by @LinZiyuu in #3653
- fix(security): bound everygrams default max_len to stop unbounded-allocation DoS (CVE-2026-12861) by @LinZiyuu in #3655
- fix(security): route DependencyGraph.load through the pathsec sandbox (CVE-2026-12867) by @LinZiyuu in #3656
- fix(security): make Cistem stemmer linear to stop quadratic-time DoS (CVE-2026-12868) by @LinZiyuu in #3658
- fix(security): route data.retrieve write through the pathsec sandbox (CVE-2026-12871) by @LinZiyuu in #3659
- fix(security): bound DRT resolve_anaphora work to stop quadratic time+memory DoS (CVE-2026-12873) by @LinZiyuu in #3660
- fix(security): bound chart parse-tree extraction to stop exponential DoS (CVE-2026-12886) by @LinZiyuu in #3661
- fix(security): make valuation separator regex linear to stop quadratic ReDoS (CVE-2026-12890) by @LinZiyuu in #3662
- fix(security): make feature-structure rename regex linear to stop quadratic ReDoS (CVE-2026-12919) by @LinZiyuu in #3663
- fix: enforce package ownership during ZIP extraction by @ibfavas in #3625
- fix(security): block IPv6 transition-embedded internal IPv4 in SSRF filter (NAT64 bypass, CWE-918) by @LinZiyuu in #3644
- fix(security): bound generate() derivation-expansion steps to stop unbounded-enumeration DoS (CVE-2026-12864) by @LinZiyuu in #3654
- fix(security): bound phrase_extraction default max_phrase_length to stop cubic DoS (CVE-2026-12870) by @LinZiyuu in #3657
- Prevent Arbitrary File Write via XML A...
v3.10.0-rc1
What's Changed
- CHILDES corpus reader: lazy loading (fixes #1340) by @nschneid in #1341
- Stanseg by @casperlehmann in #1350
- Add optional CCG semantics computation by @tanin47 in #1321
- address of free software foundation updated by @Lightyagami1 in #1347
- Added a verification for empty tokens by @gnardari in #1353
- use data.load for vader lexicon by @afgiel in #1303
- Ensure that proper exceptions are caught correctly by @pombredanne in #1343
- Reset CCGVar everytime we invoke lexicon.fromstring() and skip the unicode test because it fails on python2.7 by @tanin47 in #1354
- Add tanin as an author :) by @tanin47 in #1355
- framenet.py: remove unnecessary str() call that causes problems for names with Unicode characters by @nschneid in #1360
- remove
ubecause it doesn't work in Python 3.0 to 3.2 by @tanin47 in #1362 - moved the import for dispersion_plot out of the else block by @dennisobrien in #1364
- Raise warning for infinite recursion by @alvations in #1365
- Patch None TypeError in align_blocks() by @alvations in #1366
- Safer try-except for catching infinite recursion by @alvations in #1368
- Collections refactor by @nschneid in #1382
- Handles fringe cases in BLEU by @alvations in #1383
- Allow specifying substitution cost in edit distance by @dnc1994 in #1386
- Pl196x fixes by @pkasprzyk in #1359
- Docs/ngrams by @ColCarroll in #1403
- Fix conll.py by @germanferrero in #1402
- Aline by @geoffbacon in #1407
- Handle empty files in a corpus by @alexisdimi in #1411
- fixed bug: StackDecoder would throw exception if it's unable to finish… by @hikui in #1405
- add hikui to authors by @hikui in #1412
- removes download-cache pip flag from tox config by @fievelk in #1415
- Added three tokenizers, one detokenizer and two tokenizer-related word/char list corpora by @alvations in #1282
- Much faster Vader sentiment tagging by @georgeberry in #1409
- Fixes doctest in is_cjk() and REPP tokenizer wrapper by @alvations in #1420
- Fixed doctest for UnicharsCorpusReader and NonbreakingPrefixesCorpusReader by @alvations in #1422
- Updated senna version in docstring by @alvations in #1431
- Removes Python 2.6 and Python 3.1-3.3 code, Improves Python 3.5 syntax by @adamn in #1429
- Cleanup by @dimazest in #1432
- Fix zero division & log(0) in PunktTrainer._col_log_likelihood by @mrecachinas in #1247
- Update stanford_segmenter.py by @janissl in #1444
- explained data format for constructor by @drevicko in #1450
- Use a more efficient idiom for flattening nested lists by @rmalouf in #1447
- improved documentation of load_array() by @drevicko in #1452
- changed "list or iterator" to "sequence" by @drevicko in #1453
- Deal gracefully with degenerate cases. by @drevicko in #1454
- deal with (hopefully) all degenerate cases by @drevicko in #1455
- Enable unordered comparison for gaac clustering by @alvations in #1456
- Rewrite porter.py by @ExplodingCabbage in #1261
- Update naivebayes.py by @andyreagan in #1461
- Fixes bug where download status not returned. by @nsfabina in #1468
- Use lcon and rcon variables in conllned() when trace is False - Issue #1474 by @timleslie in #1475
- Use shutil.copyfileobj for unzipping by @ikorolev93 in #1478
- Fixing Chen&Cherry method 6 smoothing in BLEU score by @alvations in #1477
- Fix undefined variable bugs by @timleslie in #1473
- modified pos_tag and pos_tag_sents for russian pos-tagging by @tsolakghukasyan in #1480
- updated jenkins.sh to reflect the new address of the stanford tagger by @muneson in #1493
- Final tests and tweaks to fix BLEU by @alvations in #1488
- Replace old floating point division by @prasastoadi in #1508
- Fix error on BLEU unittest by @alvations in #1505
- Fix tox dependencies by @fievelk in #1497
- Make tox aware of environment variables by @fievelk in #1500
- Added ChrF and GLEU MT evaluation metrics by @alvations in #1496
- Fix SENNA tests by @fievelk in #1499
- Fix TransitionParser tests by @fievelk in #1501
- New style string formatting by @prasastoadi in #1512
- Fix Aline tests by @fievelk in #1502
- Fixed a PATH separator in stanford_segmenter.py by @janissl in #1511
- Issues warning upon calling deprecated generate() function by @SaintNazaire in #1514
- Clean tox output hiding warnings by @fievelk in #1503
- Fixes several errors in remove_handles by @hdlim15 in #1521
- Added test cases for remove_handle by @hdlim15 in #1522
- Fixes StopIteration warnings when casting empty list to Counter object by @alvations in #1529
- Fixed missing parenthesis on MASI distance found by @drevicko by @DrDub in #1535
- Checks that order of ngram >= length of sentence to avoid Python3.5 warning by @alvations in #1532
- Further improve tests output by @fievelk in #1536
- Strip trailing whitespace in sentence splitting with Punkt. by @lfurrer in #1534
- Add PyPI version badge to README by @fievelk in #1547
- Add optional dependencies for NLTK installation by @fievelk in #1538
- Fix failing doctest on chrf_score by @alvations in #1546
- Fix typo in
quote_countsvariable by @alvations in #1550 - LazyIteratorList bugfix/simplification by @nschneid in #1555
- Fix Moses Tokenizer and Detokenizer by @alvations in #1553
- FrameNet corpus reader overhaul by @nschneid in #1556
- Fixing SmoothingFunction() in BLEU by @alvations in #1557
- Add upside exclamation point to ToktokTokenizer by @naoyak in #1561
- FrameNet test fixes by @nschneid in #1563
- FrameNet usability improvements: help() overview of methods; URLs by @nschneid in #1564
- FrameNet: further test fixes by @nschneid in #1565
- FrameNet: Test cases that should pass with FrameNet 1.5 by @nschneid in #1566
- FrameNet: more fixes to the tests by @nschneid in #1567
- FrameNet: need 3 dots for doctest line continuation by @nschneid in #1568
- framenet.doctest: Apparently corpus ID changed from FN 1.5 to 1.7 by @nschneid in #1569
- FrameNet 1.5 compatibility by @nschneid in #1570
- framenet.doctest: sentence ID has changed between FN 1.5 and 1.7 by @nschneid in #1571
- FrameNet 1.7 compatibility in doctests by @nschneid in #1572
- FrameNet 1.7 compatibility in doctests by @nschneid in #1573
- Fix unclosed files in porter test causing ResourceWarnings by @ExplodingCabbage in #1583
- Fix Porter stemmer failing on 'oed' by @ExplodingCabbage in #1582
- Fix Stanford interfaces by @fievelk in #1585
- Ensure file pointer is closed in xmldocs.py by @safwank in #1587
- Checks for string equality to fix nltk.util.escape_xml doctest by @alvations in #1578
- fix bug about utf-8 space by @xinyuan-liu in #1549
- Fix StanfordNeuralDependencyParser tests by @fievelk in #1588
- Handle deprecated getargspec by @naoyak in #1...