Skip to content

Delete temp file when http4s request body stream fails (413 leak)#5385

Merged
adamw merged 2 commits into
masterfrom
fix/http4s-tempfile-leak-on-413
Jul 7, 2026
Merged

Delete temp file when http4s request body stream fails (413 leak)#5385
adamw merged 2 commits into
masterfrom
fix/http4s-tempfile-leak-on-413

Conversation

@adamw

@adamw adamw commented Jul 7, 2026

Copy link
Copy Markdown
Member

The leak

In Http4sRequestBody, the RawBodyType.FileBody case creates a temp file via serverOptions.createFile and then compiles the body stream into it. If the stream fails mid-write — e.g. with StreamMaxLengthExceededException when the configured max content length is exceeded (resulting in a 413) — the RawValue(FileRange(file), ...) is never produced. The interpreter's body-cleanup logic only deletes files that were registered as raw values, so the temp file is never deleted.

The leak happens on every over-limit file upload, making it a disk-fill DoS vector: a client can fill the server's temp directory simply by repeatedly sending oversized bodies to a file-body (or multipart file) endpoint.

#5335 fixed exactly this in the akka, pekko, jdk-http, netty (cats/future/sync/zio) and zio-http backends, but missed http4s.

Why the shared test only catches it intermittently

The shared test checks payload limit and returns 413 on exceeded max content length (request) (ServerBasicTests) polls for leftover temp files containing the test's marker bytes. Whether the leaked file contains those bytes depends on how the HTTP client chunks the request and how much of the body was written before the limit interrupt — in local runs the leaked file is often 0 bytes, so the marker check misses it (and the oversized-request send may also end in the SttpClientException recover path). The leak itself is deterministic; only its detection is chunking-dependent. This is the source of the flaky Http4sServerTest 413 failure seen e.g. on #5373's CI.

The fix

Attach an onError handler to the stream compilation that deletes the file via serverOptions.deleteFile. onError rethrows the original error after running the handler, and the handler swallows its own failures (.attempt.void) so a failing delete cannot mask the original error. Since toRawFromStream is also used for multipart parts and by the http4s-zio interpreter, those are covered too.

Verification

  • Baseline (unmodified master): http4sServer/testOnly sttp.tapir.server.http4s.Http4sServerTest -- -z 413 passes (5/5) but leaves 1 leaked /tmp/tapir*tmp file (0 bytes — confirming why the marker-based detection is intermittent).
  • With the fix: the same 413 subset run 3 times — all green, 0 leaked temp files after each run.
  • http4sServer2_12/compile, http4sServer3/compile, http4sServerZio/compile, http4sServerZio3/compile all succeed.
  • Full Http4sServerTest suite: 302 tests, all passed, 0 leaked temp files.

🤖 Generated with Claude Code

adamw and others added 2 commits July 7, 2026 11:21
When a request body is read into a file (RawBodyType.FileBody) and the
body stream fails mid-write - e.g. with StreamMaxLengthExceededException
when the configured max content length is exceeded (413) - the temp file
created by createFile was never returned as a RawValue, so the
interpreter's cleanup logic never saw it and it was never deleted. Every
over-limit file upload thus leaked a temp file on disk.

#5335 fixed exactly this in the akka, pekko, jdk-http, netty and
zio-http backends, but missed http4s. The shared test "checks payload
limit and returns 413 on exceeded max content length (request)" only
detects the leak intermittently on CI (the leaked file must contain the
test's marker bytes, which depends on chunking), which showed up as
flaky Http4sServerTest failures.

The fix deletes the file via serverOptions.deleteFile when the stream
compilation fails; onError rethrows the original error, and the handler
swallows its own failures so it cannot mask it. This also covers
multipart file parts and the http4s-zio interpreter, which use the same
class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adamw adamw merged commit a122c5e into master Jul 7, 2026
22 checks passed
@adamw adamw deleted the fix/http4s-tempfile-leak-on-413 branch July 7, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant