Skip to content

fix(http): limit buffered request bodies - #15

Merged
Upd4ting merged 4 commits into
mainfrom
fix/request-body-limit
Aug 21, 2026
Merged

fix(http): limit buffered request bodies#15
Upd4ting merged 4 commits into
mainfrom
fix/request-body-limit

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Aug 21, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

Confirmed resource-exhaustion finding from the v0.0.11 performance review.

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

ReadBody previously retained every externally controlled request chunk and then allocated a second contiguous buffer with Buffer.concat, without any byte limit. A 128 MiB request reached about 311 MiB peak RSS in the review benchmark.

This change:

  • limits buffered request bodies to 1 MiB by default;
  • rejects an oversized declared Content-Length before attaching body listeners;
  • counts received bytes so chunked requests and false length declarations cannot bypass the limit;
  • pauses the request and removes owned listeners immediately after overflow;
  • rejects with HTTPResult(413, "Payload Too Large"), which the AntelopeJS API implementation preserves as an HTTP 413 response;
  • accepts optional byte limits in ReadBody, @RawBody, and @JSONBody;
  • documents larger buffered-body opt-in and raw-request streaming.

Compatibility

Normal bodies up to and including 1 MiB keep their existing Buffer/JSON behavior. Applications intentionally buffering larger bodies must pass an explicit limit, for example @RawBody(10 * 1024 * 1024). Applications can consume context.rawRequest directly when bodies should be streamed rather than buffered. Because bodies over 1 MiB now reject by default, this is marked as a compatibility-affecting security fix.

Verification

  • pnpm run lint — passed
  • pnpm run build — passed; declarations expose limit?: number
  • pnpm --package=@antelopejs/core dlx ajs module test . — 80 passing
  • GitHub ubuntu CI, GitGuardian, and Greptile checks — passed
  • Greptile — 5/5 confidence, zero unresolved comments after three resolved findings
  • Regression coverage includes normal bodies, exact-boundary acceptance, declared and chunked overflow, HTTP 413 result shape, pausing, listener cleanup, independent concurrent limits, cached bodies, and late permissive resumption.

Benchmark

Final representative synthetic benchmark of the built code: 11 independent processes per case, 7 measured repetitions per process (77 samples), 64 KiB chunks, explicit GC before each repetition, and GNU time for process peak RSS.

Final case Median (77-sample range) Highest max RSS Highest observed external memory
1 MiB accepted 1.13 ms (0.42–3.38 ms) 55.4 MiB 6.9 MiB
128 MiB declared, rejected before reading 0.07 ms (0.06–0.94 ms) 50.3 MiB 1.9 MiB
128 MiB chunked source, rejected after crossing 1 MiB 0.40 ms (0.26–2.62 ms) 52.0 MiB 4.2 MiB

Compared with the pre-Greploop PR run (7 repetitions in one process per mode):

Case Pre-Greploop Final Reported delta Pre → final max RSS
1 MiB accepted 0.79 ms 1.13 ms +0.34 ms 55.0 → 55.4 MiB
128 MiB declared 0.05 ms 0.07 ms +0.02 ms 47.9 → 50.3 MiB
128 MiB chunked 0.38 ms 0.40 ms +0.02 ms 51.6 → 52.0 MiB

Compared with the original unbounded v0.0.11 review measurements:

Case/reference Original Final Reported timing delta Reported RSS delta External-memory delta
1 MiB accepted 3.46 ms / 51.2 MiB 1.13 ms / 55.4 MiB -2.33 ms +4.2 MiB not reported originally
128 MiB declared vs original 128 MiB read 171.36 ms / 310.6 MiB 0.07 ms / 50.3 MiB -171.29 ms -260.3 MiB 257.4 → 1.9 MiB (-255.5 MiB)
128 MiB chunked vs original 128 MiB read 171.36 ms / 310.6 MiB 0.40 ms / 52.0 MiB -170.96 ms -258.6 MiB 257.4 → 4.2 MiB (-253.2 MiB)

The final run uses more independent processes than the pre-Greploop run, so small timing and startup-RSS differences should not be overinterpreted. The decisive result is that both pathological cases remain near process-baseline memory rather than scaling with the 128 MiB source size.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Greptile Summary

The PR limits buffered HTTP request bodies while preserving independent limits for multiple consumers.

  • Adds a 1 MiB default limit with early Content-Length rejection and byte-counted streaming enforcement.
  • Supports configurable limits in ReadBody, @RawBody, and @JSONBody.
  • Retains partial data so a later permissive consumer can resume after an earlier stricter rejection.
  • Adds regression coverage and documents larger-body opt-in and direct streaming.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/index.ts Implements bounded request-body buffering, per-consumer enforcement, and recovery when a later consumer raises the shared physical-read limit.
src/tests/index.test.ts Covers boundary acceptance, declared and streamed overflow, listener cleanup, independent consumer limits, cached bodies, and late limit increases.
docs/3.http-handling.md Documents the default limit, explicit larger limits, and the raw-request streaming alternative.

Reviews (5): Last reviewed commit: "address greptile review feedback (greplo..." | Re-trigger Greptile

Comment thread src/index.ts Outdated

Copy link
Copy Markdown
Member Author

@greptile review

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread src/index.ts Outdated

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread src/index.ts

Copy link
Copy Markdown
Member Author

@greptile review

@Upd4ting
Upd4ting merged commit 4955b14 into main Aug 21, 2026
3 checks passed
@Upd4ting
Upd4ting deleted the fix/request-body-limit branch August 21, 2026 14:11
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