Gotenberg has an unauthenticated denial of service via echo.Context pool reuse in webhook async goroutine
Date: 2026-05
Affected: go/github.com/gotenberg/gotenberg/v8
Attack vector: dos
Category: vulnerability-disclosure
Severity: High
CVE: CVE-2026-42594
CWE: CWE-362
CVSS: 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

## Summary The 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. ## Details `pkg/modules/webhook/middleware.go:338-382` starts the async goroutine and immediately returns `api.ErrAsyncProcess` to the caller: ```go w.asyncCount.Add(1) go func() { defer cancel() defer w.asyncCount.Add(-1) err := next(c) // line 343 ... sendOutputFile(sendOutputFileParams{ ctx: ctx, ... }) }() return api.ErrAsyncProcess // line 382 ``` `pkg/modules/api/middlewares.go:356-361` sees the sentinel, responds with `204 No Content`, and lets Echo return `c` to the pool: ```go if errors.Is(err, ErrAsyncProcess) { return c.NoContent(http.StatusNoContent) } ``` Echo's router calls `c.Reset()` before serving the next request from the same goroutine pool slot, wiping `c.s


Mitigations:

Architectural layers (MAESTRO):