CVE-2026-126300: NLTK: quadratic CPU DoS in TreebankWordTokenizer spans

GHSA-ffr9-mgrr-wcvrMEDIUM
Published November 13, 2026
CISO Take

NLTK's TreebankWordTokenizer.span_tokenize has an O(n^2) quote-restoration loop, so an attacker who submits a long run of double-quote characters can pin a CPU core. The PoC took 6.57 seconds for 256,000 characters, against 0.16 seconds for a comparable control input. It needs no authentication or user interaction, but it affects availability only, which is why the score is medium (CVSS 5.3). Exploitation risk is moderate: the package has about 16,980 downstream dependents, but there is no public exploit, no Nuclei template, no KEV listing and no EPSS score yet. Upgrade to nltk 3.10.4, which contains the fix. Until then, cap input sizes at the application boundary for any service that span-tokenizes untrusted text.

Sources:NVDGitHub AdvisoryATLASOpenSSFVulnCheck

What is the risk?

Medium. The attack is trivial and unauthenticated (AV:N/AC:L/PR:N/UI:N), but it has no confidentiality or integrity impact. Exposure depends on whether untrusted text reaches span_tokenize, for example in offset, annotation or highlighting pipelines. A single request costs several seconds of CPU, and concurrent requests could exhaust a worker pool. That scenario is inferred from the per-request cost, not tested against a live deployment. There are no known exploitation campaigns.

How does the attack unfold?

Entry point
The attacker submits untrusted text to a network-reachable service that calls span_tokenize.
AML.T0049
Payload crafting
The attacker builds a very large run of double-quote characters, such as '"' * 256000, which triggers the quote-restoration branch.
AML.T0034.001
Exploitation
The tokenizer calls list.pop(0) once per quote token, which gives O(n^2) CPU work with no length cap or timeout.
Impact
One core is saturated for seconds per request, and concurrent requests exhaust the worker pool and degrade availability.
AML.T0029

What systems are affected?

PackageEcosystemVulnerable RangePatched
Tokenizerspip>= 3.2.5, <= 3.10.33.10.4
11.2KOpenSSF 6.017.0K dependentsPushed 7d ago80% patched~0d to patchFull package profile →

Do you use Tokenizers? You're affected.

How severe is it?

CVSS 3.1
5.3 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACLow
PRNone
UINone
SUnchanged
CNone
INone
ALow

What should I do?

5 steps
  1. Upgrade nltk to 3.10.4 or later (affected: >= 3.2.5, <= 3.10.3).

  2. Until you can upgrade, enforce maximum request and input length limits before text reaches span_tokenize.

  3. Apply per-request CPU or wall-clock timeouts, and run tokenization in isolated workers with concurrency limits and rate limiting.

  4. Find usages with a code search for span_tokenize and TreebankWordTokenizer, and check them with pip show nltk or an SBOM scan.

  5. Detect abuse by alerting on requests containing very long runs of repeated quote characters and on sustained single-core CPU spikes in NLP workers.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

ISO 42001
A.6.2.6-AI system operation and monitoring
NIST AI RMF
MANAGE 2.3-Responding to unknown risks and resource constraints
OWASP LLM Top 10
LLM10-Unbounded Consumption

Frequently Asked Questions

What is CVE-2026-126300?

NLTK's TreebankWordTokenizer.span_tokenize has an O(n^2) quote-restoration loop, so an attacker who submits a long run of double-quote characters can pin a CPU core. The PoC took 6.57 seconds for 256,000 characters, against 0.16 seconds for a comparable control input. It needs no authentication or user interaction, but it affects availability only, which is why the score is medium (CVSS 5.3). Exploitation risk is moderate: the package has about 16,980 downstream dependents, but there is no public exploit, no Nuclei template, no KEV listing and no EPSS score yet. Upgrade to nltk 3.10.4, which contains the fix. Until then, cap input sizes at the application boundary for any service that span-tokenizes untrusted text.

Is CVE-2026-126300 actively exploited?

No confirmed active exploitation of CVE-2026-126300 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-126300?

1. Upgrade nltk to 3.10.4 or later (affected: >= 3.2.5, <= 3.10.3). 2. Until you can upgrade, enforce maximum request and input length limits before text reaches span_tokenize. 3. Apply per-request CPU or wall-clock timeouts, and run tokenization in isolated workers with concurrency limits and rate limiting. 4. Find usages with a code search for span_tokenize and TreebankWordTokenizer, and check them with pip show nltk or an SBOM scan. 5. Detect abuse by alerting on requests containing very long runs of repeated quote characters and on sustained single-core CPU spikes in NLP workers.

What systems are affected by CVE-2026-126300?

This vulnerability affects the following AI/ML architecture patterns: NLP preprocessing pipelines, RAG ingestion pipelines, text annotation services, ML library integrations.

What is the CVSS score for CVE-2026-126300?

CVE-2026-126300 has a CVSS v3.1 base score of 5.3 (MEDIUM).

What is the AI security impact?

Affected AI Architectures

NLP preprocessing pipelinesRAG ingestion pipelinestext annotation servicesML library integrations

MITRE ATLAS Techniques

AML.T0029Denial of AI Service
AML.T0034.001Resource-Intensive Queries

Compliance Controls Affected

ISO 42001: A.6.2.6
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM10

What are the technical details?

Original Advisory

## Summary `TreebankWordTokenizer.span_tokenize` in the NLTK tokenizer package contains an unguarded O(n^2) processing loop in its quote-restoration step. When the method is invoked on text containing a long run of double-quote characters (for example `'"' * n`), it performs a `list.pop(0)` operation once per quote token, and because `list.pop(0)` is O(length), the total cost grows with the square of the input length. An attacker who can supply untrusted text to this public span-tokenization API can force a single request to saturate one CPU core for a prolonged period, degrading availability. No authentication, special configuration, or user interaction is required. ## Root Cause Inside `span_tokenize`, when the input contains double or repeated quotes, the code builds `matched`, a list with one entry per quote occurrence, and then reconstructs tokens in a comprehension that calls `matched.pop(0)` for each token that is a quote. Removing the first element of a Python list is an O(len(list)) operation because all remaining elements are shifted. For an all-quote input the tokenizer produces n quote tokens and `matched` has n entries, so `pop(0)` is called n times, each time on a list of shrinking but still linear length, giving cumulative O(n^2) CPU work. The violated invariant is bounded per-input work: there is no length cap on `span_tokenize` or the underlying `tokenize`, so untrusted input length translates into quadratic processing time. The neighboring `align_tokens` call is linear and is not the quadratic term. ```text Affected function: TreebankWordTokenizer.span_tokenize Source: nltk/tokenize/treebank.py - branch selector at line 203 - matched list built at line 205 - quadratic sink matched.pop(0) at line 209 - linear align_tokens call at line 215 ``` ## Impact An attacker who controls text passed to `TreebankWordTokenizer.span_tokenize` can trigger quadratic CPU work. In the reviewed release, an all-quote input of 128,000 characters takes about 1.76 seconds and 256,000 characters about 6.57 seconds on a single core, with cost growing as the square of the length. Memory use is linear. The demonstrated capability is single-core CPU exhaustion producing denial of service or latency degradation in any network-reachable component that uses span tokenization on untrusted input, such as offset, annotation, or highlighting pipelines. There is no confidentiality or integrity impact. The plausible operational consequence, where multiple concurrent requests are used, is worker-pool exhaustion; this is inferred from the measured per-request cost and was not executed against a live deployment. ## Proof of Concept `Requires only the released nltk package; no corpus download is needed.` 1. Instantiate `TreebankWordTokenizer`. 2. Call `span_tokenize` on an all-double-quote string of increasing length and time each call. 3. Compare against an equal-token-count control input that avoids the quote branch. ```python import time from nltk.tokenize import TreebankWordTokenizer tok = TreebankWordTokenizer() for n in (32000, 64000, 128000, 256000): payload = '"' * n start = time.perf_counter() list(tok.span_tokenize(payload)) print("quote-run", n, round(time.perf_counter() - start, 3)) control = "a " * 128000 # comparable token count, avoids the pop(0) branch start = time.perf_counter() list(tok.span_tokenize(control)) print("control", round(time.perf_counter() - start, 3)) ``` ```text quote-run 32000 0.23 quote-run 64000 0.52 quote-run 128000 1.76 quote-run 256000 6.57 (about 4x per doubling => O(n^2)) control 0.16 (roughly 11x faster, isolates pop(0)) ``` ## Attack Chain 1. **Exposure:** `TreebankWordTokenizer` is exported from `nltk.tokenize` and `span_tokenize` is a public, documented method reachable by any service that span-tokenizes untrusted text. 2. **Control:** The attacker fully controls the `text` argument; a run of double-quote characters such as `'"' * n` survives to the quote branch unchanged. 3. **Path:** The branch selector at line 203 (`'"' in text` or `"''" in text`) is satisfied by the payload, entering the vulnerable branch; `matched` is built at line 205 with n entries and the reconstruction comprehension runs at line 209. 4. **Guard:** There is no length cap on `span_tokenize` or `tokenize`. The regex timeout backstop in `nltk/redos.py` only wraps regular-expression operations and does not cover the pure-Python `list.pop(0)` sink; a 256,000-character input ran to completion in 6.57 seconds with no timeout raised. 5. **Primitive:** `matched.pop(0)` executed once per quote token, each call O(len(matched)), yields cumulative O(n^2) CPU work. 6. **Result:** A single request with a large quote-run input exhausts one CPU core for a sustained period, degrading service availability. ## Bypass Evidence No guard blocks this path. Direct inspection found no `MAX_TOKEN_LEN` or length check in `nltk/tokenize/treebank.py`, in contrast to the sibling `sonority_sequencing.py` which does cap token length. The strongest refutations were attempted and failed: (1) the hypothesis that `tokenize` collapses repeated quotes so few `pop(0)` calls occur was disproven by observing that `tokenize('"' * n)` yields exactly n quote tokens and timing showing about 4x growth per doubling, while an equal-token-count non-quote control ran roughly 11x faster; (2) the hypothesis that the project regex-timeout backstop already caps this cost was disproven by running a 256,000-character input to normal completion in 6.57 seconds with no `TimeoutError`, confirming the backstop wraps only regex operations and not `list.pop(0)`; (3) the hypothesis that this line was already audited was disproven, since the project quadratic-DoS test suite explicitly clears the neighboring `align_tokens` line reached through `span_tokenize` but contains no test for the `pop(0)` loop, an audited-neighbor pattern where the adjacent line was checked and this one was missed. The seed remediation touched a different module and no advisory covers this sink. ## Affected Versions - `Ecosystem: pip` - `Package: nltk` - `Confirmed affected range: >= 3.2.5, <= 3.10.3` - `Latest release checked: 3.10.3 (PyPI)` - `Fix status: not fixed` The vulnerable `pop(0)` loop is present and unguarded in the current released artifact 3.10.3, verified by direct source inspection and by reproducing the quadratic curve through fresh execution. The lower bound of 3.2.5 reflects when the affected quote-restoration code shipped; the disclosure-relevant fact is that the latest published release is affected and no fix has been released. ## Suggested Fix Replace the O(n) `list.pop(0)` pattern with an O(1) consumption strategy, for example iterating over `matched` with an index or an iterator, or using `collections.deque` with `popleft()`, so that the quote-restoration step runs in linear time overall. Additionally, consider enforcing a configurable maximum input length in `span_tokenize` consistent with the caps applied to sibling tokenizers, and ensure the fix covers all callers that pass untrusted text. As an interim mitigation, deployments that expose span tokenization of untrusted input can cap request and input sizes at the application boundary to bound the worst-case length. Reported by zx (GitHub: @manus-pi). ## Fix status (2026-09-28) Two pull requests carry a fix. PR: #3753 "[Do not merge yet] Fix GHSA-8mgp-746c-j5xp: URL-scheme bypass of pathsec.validate_path", open; head da55cab0e at the time of this note (a develop merge on top of 238f81500, the head on which the verdicts below were measured). Fix commit: cf12a5e6a (the quote-restore loop in tokenize/treebank.py and tokenize/destructive.py iterates forward instead of list.pop(0)). PR: #3895 "[WIP] Bound algorithmic-DoS and uncontrolled-recursion vectors (CWE-400/407/674/1333)", open, head b22ebe144. Fix commit: e20c40e26 "Bound tokenizer/parser algorithmic DoS (ffr9/gpwc/8fx7)". No probe. The advisory PoC re-run (span_tokenize over 32k and 128k double quotes): #3753 head 3.5x over a 4x input, #3895 head 3.2x, both linear. On develop 2fa3f48fe the 128k run did not finish within 120 s, so develop is still VULNERABLE.

Exploitation Scenario

An attacker finds a public text-analysis or document-highlighting API backed by NLTK that returns token offsets. They send a request containing a run of 250,000 or more double-quote characters. Each request holds one CPU core for several seconds. By sending a modest number of concurrent requests, the attacker exhausts the worker pool, and legitimate users of the NLP or RAG ingestion service see timeouts.

Weaknesses (CWE)

CWE-400 — Uncontrolled Resource Consumption: The product does not properly control the allocation and maintenance of a limited resource.

  • [Architecture and Design] Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
  • [Architecture and Design] Mitigation of resource exhaustion attacks requires that the target system either: The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question. The second solution is simply difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply makes the attack require more resources on the part of the attacker. recognizes the attack and denies that user further access for a given amount of time, or uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

Timeline

Published
November 13, 2026
Last Modified
November 13, 2026
First Seen
November 14, 2026

Related Vulnerabilities