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 string (PR #3785)
- Isolate MTE tag filters per view (PR #3792)
- Fix newline in ellipsis tokens in TweetTokenizer (PR #3819)
- Isolate default WordNet reference relations (PR #3872)
- Fix resolution paths that cancel distinct literals (PR #3878)
- Fix UTF-8 text preprocessing in NKJP corpus reader (PR #3879)
- Remove shadowed FeatStruct._walk stub and preserve its docstring (PR #3893)
- Count all increasing pairs in RIBES kendall_tau (PR #3900)
- Fix the TweetTokenizer ZWJ emoji pattern capturing adjacent whitespace (PR #3913)
- Surface the CoreNLP server's reason for a non-2xx answer (PR #3942)
- Omit unaligned (NULL target) pairs from Alignment strings, so IBM model output prints (PR #3967)
- Fix DependencyGraph cycle detection, which returned False on every parsed graph (PR #3978)
- Show, range and draw an alignment with an unaligned pair on every surface (PR #3987)
Thanks to the following contributors to 3.10.4: Zhewen Tan, Yanhaoxi, Yazan Balawneh (Cystack.ps),
Abdulmoyn Sommakieh, Golitsin Vyacheslav, Mingzhe Yu, 宁岑, Tai An, 区梓灏, Tom Aarsen, Raffi Enficiaud,
JJ, Artemiy, Cherno.x, 0xEaS, sarvesh patil, Isaac David, Arthur Gervais, leduckhuong, Ibrahim zain,
Athul Jayaram, Chiencp, Ziyu Lin, Michael Lip, GOLDBERG8, Litesh Ghute, Kartik G, Thai Son Dinh,
infy, 0xRenSec, Joshgun Abdullayev, Arpit Jain, Mohammad Favas S, Jace, nguyencanhthuong,
kokomaru167, jankesec, Daniel Coles, Fodhil Benhiba, Ubaid Ur Rehman, Maciej Zmitrukiewicz,
Gecko Security, Bugbunny, Liling Tan, Huanyi Xie, ahcrm-core, Eric Kafe, Steven Bird.
What's Changed
- Show README.md as the PyPI long description by @alvations in #3746
- Add stopwords.words("english-extended") by @alvations in #3747
- Fix pathsec.urlopen treating NO_PROXY as a configured proxy (#3748) by @alvations in #3750
- Krippendorff's alpha: treat placeholder labels as missing data (#2865, #2732) by @alvations in #3752
- Harden core security modules: pathsec URL-scheme bypass, picklesec denylist, xmlsec parse() by @alvations in #3754
- Add a living per-advisory security-probe harness by @alvations in #3749
- Harden tokenize file I/O against path traversal (punkt, stanford_segmenter) by @alvations in #3755
- Add a shared pathsec sandbox conftest fixture by @alvations in #3762
- Contain CorpusReader / agreement / chat80 file I/O in the pathsec sandbox by @alvations in #3761
- Write parse demo/profile output to a private staging dir, not /tmp or the CWD by @alvations in #3763
- Add pathsec regression tests for sem.util / xmlsec / dataset loaders by @alvations in #3764
- Harden maxent / named-entity model-artifact saves against shared-tmp planting by @alvations in #3759
- Route weka ARFF_Formatter.write through the pathsec sandbox by @alvations in #3760
- Harden untrusted-model pickle allowlist (exact globals + numpy.lib denylist) by @alvations in #3757
- Split nltk_data pickle-asset regression into always-on and skippable tests by @alvations in #3770
- Reject Java launcher switches and @argfiles in the java() cmd argument (CWE-88) by @alvations in #3771
- Prevent infinite loop from cyclic collection indices by @ekaf in #3767
- Fix ARFF label injection in Weka classifier by @ekaf in #3768
- Stabilize multiprocessing test timeouts and context handling by @ekaf in #3776
- Fix Java wrappers decoding str output from java() (universal_newlines) by @alvations in #3777
- Fix shared defaults in chat80 Concept by @tandede in #3773
- Harden internals.java() (CWE-88): options, classpath, env, discovery; route MaltParser through it by @alvations in #3772
- Bound decompression entry points in nltk (zip/gzip layers + pathsec.ZipFile + weka), CWE-409 by @alvations in #3778
- Restore pre-commit formatting and register the new probe id on develop by @alvations in #3791
- Harden pickle deserialization (picklesec) against arbitrary-code-execution gadgets by @alvations in #3794
- Fix internals.doctest to match the hardened _validate_java_options allowlist by @alvations in #3795
- Bound re_show's caller-supplied regex against ReDoS (CWE-1333) by @alvations in #3796
- Refuse CWD-relative matches for bare names in find_file/find_dir (CWE-427) by @alvations in #3798
- Bound RegexpStemmer's caller-supplied pattern against ReDoS by @alvations in #3801
- Deserialize chat80 shelve values through the restricted unpickler (RCE fix) by @alvations in #3802
- Harden path/resource/tool validation in nltk.pathsec by @alvations in #3797
- Harden downloader Package attributes and extraction against traversal/Zip-Slip by @alvations in #3804
- Harden nltk.xmlsec.parse: read filename sources through pathsec.open by @alvations in #3805
- Validate the NE chunker save_params destination through the tool-directory guard by @alvations in #3806
- Pin maxent scratch files to a data root and validate scratch IO by @alvations in #3807
- Add staging_tempdir and tighten data.py zip/staging guards by @alvations in #3808
- Stage boxer scratch input inside an nltk.data root by @alvations in #3809
- Canonicalize obfuscated numeric IPv4 in the SSRF guard (CWE-918) by @alvations in #3810
- Harden the VERSION read in nltk package init with pathsec.open_package_resource by @alvations in #3803
- Harden stanford tokenizer/segmenter wrappers: validate jar/model paths and argv by @alvations in #3812
- Harden tagger model loads and tool paths (perceptron/crf/hunpos/stanford) by @alvations in #3813
- Stage weka scratch files in a data root and validate the WEKAHOME jar path by @alvations in #3814
- Route corpus-reader scratch tempfiles through a data root and label framenet path refusals by @alvations in #3815
- Harden malt and stanford parser wrappers: validate binary/model/IO paths and argv by @alvations in #3816
- Harden nltk.tbl.demo model load/save through picklesec and pathsec by @alvations in #3817
- Add tests covering already-merged pathsec/security guards by @alvations in #3818
- Fix develop CI: register data roots for benign save/IO tests broken by pathsec hardening by @alvations in #3822
- Prevent combinatorial DoS in skipgrams by @ekaf in #3784
- Prevent IndexError in Hungarian stemmer on empty string by @ekaf in #3785
- Sanitize SFM markers in ToolboxData by @ekaf in #3800
- Isolate MTE tag filters per view by @ekaf in #3792
- Prevent classpath shadowing in GenericStanfordParser by @ekaf in #3782
- Prevent batch-boundary injection in StanfordParser by @ekaf in #3788
- Add remaining GHSA-8mgp security regression tests (pathsec/java/redos/archive/corpus) by @alvations in #3821
- Add redos.source_of/reharden and picklesec.harden_object_graph by @alvations in #3824
- Load nltk.tbl.demo models through an allowlisting unpickler by @alvations in #3823
- Bound algorithmic-complexity (time) DoS in stemmers, metrics, parsers (CWE-407 / CWE-770) by @alvations in #3827
- Bound regex compilation against a compile-time DoS by @alvations in #3825
- Route top-level nltk regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3844
- Route nltk.sentiment regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3843
- Route nltk.chat regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3831
- Route nltk.corpus regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3840
- Route nltk.parse regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3838
- Route nltk.draw regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3832
- Route nltk.stem regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3835
- Route nltk.translate regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3833
- Route nltk.sem regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3839
- Route nltk.tree regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3834
- Route nltk.tokenize regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3829
- Route nltk.chunk regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3842
- Route nltk.classify regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3837
- Route nltk.tag regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3836
- Route nltk.app regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3841
- Route nltk.ccg regexes through nltk.redos (CWE-1333/CWE-400) by @alvations in #3830
- Bound compile-time DoS (ValueError) in the concordance and nemo GUI regex handlers (CWE-1333/CWE-400) by @alvations in #3845
- Harden RegexpStemmer against pre-compiled ReDoS patterns (CWE-1333) by @alvations in #3846
- Add security regression tests + harden SSRF filter (transition tunnels, IP literals) by @alvations in #3847
- Strengthen the advisory security-probe harness (guards already on develop) by @alvations in #3820
- Guard model-artifact loaders against un-sandboxed open() by @alvations in #3848
- Route security-test fixtures through the pathsec I/O helpers by @alvations in #3849
- Stage REPP scratch input in an nltk.data root instead of the shared system temp by @alvations in #3851
- Bound JSON deserialization against deep-nesting crash and resource DoS by @alvations in #3828
- Fix newline in ellipsis tokens in TweetTokenizer by @webzuweb in #3819
- Refuse NUL bytes in pathsec.validate_path; add a pathsec attack-candidate harness by @alvations in #3852
- Re-check the normalized form of a no-protocol resource path against traversal (CWE-22) by @alvations in #3853
- Add and expand security / ReDoS attack test coverage by @alvations in #3855
- Escape corpus-derived text in the WordNet browser app (CWE-79 XSS) by @alvations in #3856
- Add test-only regression coverage for already-fixed CWEs by @alvations in #3860
- Extend the no-unsandboxed-open guard to app/draw/twitter and route every open through pathsec.open by @alvations in #3861
- Add the all-regex-through-redos pre-commit guard and two regression tests by @alvations in #3862
- Exclude pytest's ephemeral basetemp from the pickle-asset scanner by @alvations in #3864
- Open operator-chosen app/twitter paths with an annotated builtin open, not pathsec.open by @alvations in #3865
- Bound the WekaClassifier model path to the pathsec data roots by @alvations in #3863
- Tidy transitionparser security comments to one point per <=3 lines by @alvations in #3866
- Deflake anti-DoS timing tests and harden senseval _fixXML leading-run ReDoS by @alvations in #3869
- Harden and standardize NLTK's external-tool wrappers on a pathsec trusted-spawn API (CWE-426/427/732/93/88) by @alvations in #3858
- Don't miscount a python-crfsuite Windows failure as a pathsec over-block (tag sweep) by @alvations in #3876
- Give every pathsec refusal the standard Security Violation marker (spawn_trusted + ZIP audit) by @alvations in #3877
- Bound recursion depth in parsers and traversal helpers by @ekaf in #3885
- Isolate default WordNet reference relations by @vzer200 in #3872
- Harden and complete the CoreNLP server-option allowlist (CWE-88/22/502) by @alvations in #3868
- Optimize CI test matrix by @ekaf in #3884
- Harden CoreNLPServer start attempts by @ekaf in #3894
- Make DoS/ReDoS timing regression tests load-invariant (fix CI flakiness) by @alvations in #3899
- Extend no-unsandboxed-open guard to gzip/bz2/lzma/zipfile/tempfile (+ coverage test) by @alvations in #3904
- Count all increasing pairs in RIBES kendall_tau by @Nicholas022400701 in #3900
- Potential fix for code scanning alert no. 28: Clear-text logging of sensitive information by @ekaf in #3902
- Remove shadowed FeatStruct._walk stub and preserve its docstring by @Anai-Guo in #3893
- Fix resolution paths that cancel distinct literals by @UnbearableFate in #3878
- Add GHSA-j456 live probe; expand the GHSA-8mgp probe attack set by @alvations in #3905
- Handle the redos MAX_PATTERN_LENGTH refusal in help._format_tagset by @alvations in #3906
- Handle the redos MAX_PATTERN_LENGTH refusal in TokenSearcher.findall by @alvations in #3907
- Handle the redos MAX_PATTERN_LENGTH refusal for a tgrep /regex/ node literal by @alvations in #3908
- Make help._format_tagset fail closed on an oversized tag pattern (fix develop CI) by @alvations in #3910
- Add nltk.termsec terminal-output sanitizer with attack-matrix tests by @alvations in #3914
- [WIP] Fix quadratic DoS in Tree.chomsky_normal_form right-factor binarization (GHSA-r53h-rw34-8h97) by @alvations in #3909
- Bound SyllableTokenizer fallback-vowel accumulation (CWE-400 / CWE-407) by @alvations in #3911
- Fix develop CI on Python 3.10/3.11: let deepcopy reach MAX_TREE_DEPTH by @alvations in #3917
- Fix the XMLCorpusView ReDoS-test Windows CI flake (in-process op timing + Windows autoload) by @alvations in #3912
- Route every library print through the nltk.termsec.safe_print chokepoint by @alvations in #3915
- Log the perceptron training progress on the module logger (fixes the develop 3.10/3.11 cells) by @alvations in #3924
- Resolve every executed tool binary absolute-only: prover9/mace4, megam, tadm, java, hunpos (untrusted search path, CWE-426/427) by @alvations in #3887
- Pin the GHSA-cc5r-64rf-75hg proof of concept end to end and run its sink as probe phase 6 (follow-up to #3887) by @alvations in #3925
- Validate chat80.sql_query dbname for path containment (CWE-59/73) by @alvations in #3888
- Extend the tool model-file guards to crf, stanford tagger and segmenter by @alvations in #3871
- Consolidate the internals, downloader, pathsec and decorators guards from #3753 and #3850 by @alvations in #3926
- Consolidate the nltk/parse guards from #3753 and #3850 and judge their inputs on real characters by @alvations in #3927
- Sanitise the CSV cells and report lines the sentiment helpers write by @alvations in #3931
- Read a long line in linear time in SeekableUnicodeStreamReader.readline (GHSA-j8g8) by @alvations in #3932
- Escape AlignedSent DOT labels, refuse NUL, and keep the alignment checks under -O by @alvations in #3930
- Align quote tokens in linear time in the Treebank and NLTK word tokenizers by @alvations in #3934
- Never leave the pop of an escaped bidi override live in sanitize_terminal by @alvations in #3937
- Find readline's line boundary with str.splitlines, not a timed regex by @alvations in #3938
- Walk semi-relations once in relextract, and keep a pattern's flags in redos.reharden by @alvations in #3935
- Fix UTF-8 text preprocessing in NKJP corpus reader by @UnbearableFate in #3879
- Make the CoNLL SRL, TIMIT and XML view readers linear, and parse corpus XML through xmlsec (GHSA-v8f3) by @alvations in #3933
- Measure probe scaling in process CPU time so a loaded runner cannot flip a verdict by @alvations in #3939
- ci(deps): bump dorny/paths-filter from 3 to 4 in the github-actions group by @dependabot[bot] in #3943
- Surface the CoreNLP server's reason for a non-2xx answer and warm the live test server by @alvations in #3942
- Guard the Stanford tagger and segmenter input against line injection, verified on the real tools by @alvations in #3941
- Bring the self-contained tests of #3753 to develop by @alvations in #3940
- Guard the twitter CSV exports against formula and control-sequence injection, verified on the real corpus by @alvations in #3946
- Guard the maxent parameter files against outside-root paths and unrepresentable names, verified on a real trained model by @alvations in #3945
- 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) by @alvations in #3944
- [Do not merge yet] Guard tool wrappers, Prover9 input and the downloader against injection and unbounded-response DoS by @alvations in #3850
- Added RTL language syntactic tree visualization support by @m0yn in #3472
- Make the word, blankline and TextTiling tokenizers, the valuation parser, relextract and toktok linear on retried leading regex runs by @alvations in #3947
- Add the 3.10.4 commit authors and security reporters to AUTHORS.md by @alvations in #3948
- Pair the scaling samples by rep and size the under-margined probes over the floor by @alvations in #3949
- 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) by @alvations in #3950
- Support symlinked executables in pathsec by @ekaf in #3951
- [Do not merge yet] Keep downloaded archives private and serve other accounts through extraction (fixes #3928) by @alvations in #3929
- Bound algorithmic-DoS and uncontrolled-recursion vectors (CWE-400/407/674/1333) by @alvations in #3895
- Guard the two-class any-character runs and count apostrophe-named groups (follow-up to #3895) by @alvations in #3953
- Fix #3394: Ensure punkt_tab is downloaded when punkt is requested by @askubaid in #3505
- Restore add_blank_lines and to_settings_string after the GHSA-f2h2 depth bound by @alvations in #3958
- Let chat80.val_load read back what val_dump wrote by @alvations in #3959
- Keep full collections out of timed scaling samples (89p3 teeth and relextract reds) by @alvations in #3963
- CI: take the Windows Graphviz from the cached third-party tools, not Chocolatey by @alvations in #3968
- Close the chat80 store swap window and read stores by a key manifest by @alvations in #3964
- Route every module's file removal, rename, privacy test and sqlite open through pathsec by @alvations in #3969
- Fix the develop CI breakage from the fg9w advisory merge (Senna -path separator, test fixtures under /tmp) by @alvations in #3971
- Refuse relation walks deeper than MAX_RECURSION_DEPTH instead of truncating them by @alvations in #3960
- Harden the Toolbox reader and writers after the #3958 re-audit by @alvations in #3962
- Route the downloader, XML corpus view, internals and Toolbox through nltk.xmlsec and nltk.data by @alvations in #3972
- Open gzip file names through pathsec, and refuse a linked PanLex Lite database by @alvations in #3961
- Fix DependencyGraph cycle detection, dead on every parsed graph by @alvations in #3978
- Bound YAML deserialization against depth and resource DoS, verified on the real loaders by @alvations in #3974
- Guard java() cmd[0] against JEP 330 source-file execution, verified on a real JDK by @alvations in #3975
- Make Tree.repr iterative so a deep accepted tree does not overflow the stack by @alvations in #3977
- Create perceptron model files exclusively under the pinned directory (issue 3980) by @alvations in #3981
- Drop the dash-banner section dividers from the model-artifact tests by @alvations in #3982
- Skip the real-launcher test when java resolves to a runtime-less stub by @alvations in #3983
- Pin the CI megam download to https and its sha256 by @alvations in #3984
- Run the advisory probes only on a full-matrix run; keep the unit tests out of the wheel by @alvations in #3954
- Charge the shipped-form teeth leg on CPU time (Windows regex timeout is wall-clock) by @alvations in #3985
- Prepare NLTK release 3.10.4 by @ekaf in #3826
- Rewrite the 3.10.4 release notes around the user-facing security changes by @alvations in #3986
- Omit NULL targets from alignment strings by @xiehuanyi in #3967
- Fix TweetTokenizer ZWJ pattern capturing whitespace by @ahcrm-core in #3913
- Show, range and draw an alignment with an unaligned pair on every surface (follow-up to #3967, #1885) by @alvations in #3987
- Pin the TweetTokenizer ZWJ alternative: linear on repeated shapes, no whitespace absorbed (follow-up to #3913) by @alvations in #3988
- Credit every contributor since 3.10.3 in the 3.10.4 notes and AUTHORS.md; name the PRs merged after #3986 by @alvations in #3989
- Make the span_tokenize teeth control compute-bound so Windows reads its quadratic (deflake, test-only) by @alvations in #3990
New Contributors
- @tandede made their first contribution in #3773
- @webzuweb made their first contribution in #3819
- @vzer200 made their first contribution in #3872
- @Nicholas022400701 made their first contribution in #3900
- @Anai-Guo made their first contribution in #3893
- @UnbearableFate made their first contribution in #3878
- @m0yn made their first contribution in #3472
- @askubaid made their first contribution in #3505
- @xiehuanyi made their first contribution in #3967
- @ahcrm-core made their first contribution in #3913
Full Changelog: v3.10.3...v3.10.4