From a8b5af21d32ebb937f68370bd8ad984d854d06dc Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Sun, 23 Aug 2026 10:20:38 -0700 Subject: [PATCH] [GHSA-rf74-v2fm-23pw] Add nltk 3.9.4 fix and first patched version Record the published 3.9.4 fix for unbounded recursion in JSONTaggedDecoder.decode_obj. Sources: - nltk repo advisory patched_versions 3.9.4 - commit 00cdcd392142e6c745e7120c8d50a24127df5fad --- .../GHSA-rf74-v2fm-23pw/GHSA-rf74-v2fm-23pw.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/advisories/github-reviewed/2026/03/GHSA-rf74-v2fm-23pw/GHSA-rf74-v2fm-23pw.json b/advisories/github-reviewed/2026/03/GHSA-rf74-v2fm-23pw/GHSA-rf74-v2fm-23pw.json index 780279f09cb3..04c44cd4c167 100644 --- a/advisories/github-reviewed/2026/03/GHSA-rf74-v2fm-23pw/GHSA-rf74-v2fm-23pw.json +++ b/advisories/github-reviewed/2026/03/GHSA-rf74-v2fm-23pw/GHSA-rf74-v2fm-23pw.json @@ -5,7 +5,7 @@ "published": "2026-03-18T20:17:43Z", "aliases": [], "summary": "Natural Language Toolkit (NLTK) has unbounded recursion in JSONTaggedDecoder.decode_obj() may cause DoS", - "details": "### Summary\n`JSONTaggedDecoder.decode_obj()` in `nltk/jsontags.py` calls itself \nrecursively without any depth limit. A deeply nested JSON structure \nexceeding `sys.getrecursionlimit()` (default: 1000) will raise an \nunhandled `RecursionError`, crashing the Python process.\n\n### Affected code\nFile: `nltk/jsontags.py`, lines 47–52\n```python\n@classmethod\ndef decode_obj(cls, obj):\n if isinstance(obj, dict):\n obj = {key: cls.decode_obj(val) for (key, val) in obj.items()}\n elif isinstance(obj, list):\n obj = list(cls.decode_obj(val) for val in obj)\n```\n\n### Proof of Concept\n```python\nimport sys, json\nfrom nltk.jsontags import JSONTaggedDecoder\n\ndepth = sys.getrecursionlimit() + 50 # e.g. 1050\npayload = '{\"x\":' * depth + \"null\" + \"}\" * depth\n\n# Raises RecursionError, crashing the process\njson.loads(payload, cls=JSONTaggedDecoder)\n```\n\n### Impact\nAny code path that passes externally-supplied JSON to \n`JSONTaggedDecoder` is vulnerable to denial of service.\nThe severity depends on whether such a path exists in the \ncalling code (e.g. `nltk/data.py`).\n\n### Suggested Fix\nAdd a depth parameter with a hard limit:\n```python\n@classmethod\ndef decode_obj(cls, obj, _depth=0):\n if _depth > 100:\n raise ValueError(\"JSON nesting too deep\")\n if isinstance(obj, dict):\n obj = {key: cls.decode_obj(val, _depth + 1) \n for (key, val) in obj.items()}\n elif isinstance(obj, list):\n obj = list(cls.decode_obj(val, _depth + 1) for val in obj)\n```", + "details": "### Summary\n`JSONTaggedDecoder.decode_obj()` in `nltk/jsontags.py` calls itself \nrecursively without any depth limit. A deeply nested JSON structure \nexceeding `sys.getrecursionlimit()` (default: 1000) will raise an \nunhandled `RecursionError`, crashing the Python process.\n\n### Affected code\nFile: `nltk/jsontags.py`, lines 47\u201352\n```python\n@classmethod\ndef decode_obj(cls, obj):\n if isinstance(obj, dict):\n obj = {key: cls.decode_obj(val) for (key, val) in obj.items()}\n elif isinstance(obj, list):\n obj = list(cls.decode_obj(val) for val in obj)\n```\n\n### Proof of Concept\n```python\nimport sys, json\nfrom nltk.jsontags import JSONTaggedDecoder\n\ndepth = sys.getrecursionlimit() + 50 # e.g. 1050\npayload = '{\"x\":' * depth + \"null\" + \"}\" * depth\n\n# Raises RecursionError, crashing the process\njson.loads(payload, cls=JSONTaggedDecoder)\n```\n\n### Impact\nAny code path that passes externally-supplied JSON to \n`JSONTaggedDecoder` is vulnerable to denial of service.\nThe severity depends on whether such a path exists in the \ncalling code (e.g. `nltk/data.py`).\n\n### Suggested Fix\nAdd a depth parameter with a hard limit:\n```python\n@classmethod\ndef decode_obj(cls, obj, _depth=0):\n if _depth > 100:\n raise ValueError(\"JSON nesting too deep\")\n if isinstance(obj, dict):\n obj = {key: cls.decode_obj(val, _depth + 1) \n for (key, val) in obj.items()}\n elif isinstance(obj, list):\n obj = list(cls.decode_obj(val, _depth + 1) for val in obj)\n```", "severity": [ { "type": "CVSS_V4", @@ -26,11 +26,14 @@ "introduced": "0" }, { - "last_affected": "3.9.3" + "fixed": "3.9.4" } ] } - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 3.9.3" + } } ], "references": [ @@ -38,6 +41,10 @@ "type": "WEB", "url": "https://github.com/nltk/nltk/security/advisories/GHSA-rf74-v2fm-23pw" }, + { + "type": "WEB", + "url": "https://github.com/nltk/nltk/commit/00cdcd392142e6c745e7120c8d50a24127df5fad" + }, { "type": "PACKAGE", "url": "https://github.com/nltk/nltk" @@ -52,4 +59,4 @@ "github_reviewed_at": "2026-03-18T20:17:43Z", "nvd_published_at": null } -} \ No newline at end of file +}