Skip to content

fix: cap HTTP response body reads to prevent heap exhaustion - #382

Open
msuitcase wants to merge 2 commits into
masterfrom
sec-787-bounded-response-body
Open

msuitcase wants to merge 2 commits into
masterfrom
sec-787-bounded-response-body

Conversation

@msuitcase

@msuitcase msuitcase commented Oct 3, 2026 •

Copy link
Copy Markdown

Description

Fixes SEC-787 (pentest finding, Medium). Requestor read whole HTTP response bodies into memory with no size limit (Scanner(...).useDelimiter("\\A")). A compromised upstream, a misbehaving proxy, or a MITM sending a very large body could exhaust the JVM heap.

Changes:

  • One bounded read helper. getResponseBody(InputStream, long contentLength) reads in 8 KB chunks and stops at Constants.Http.MAX_RESPONSE_BODY_BYTES (10 MB).
    • Content-Length is checked up front. If the declared length is over the cap, it throws before reading anything.
    • The cap is also enforced while streaming. This covers responses where Content-Length is missing (chunked) or understated.
    • Oversized bodies throw HttpError with a descriptive message (Constants.ErrorMessages.RESPONSE_BODY_TOO_LARGE), not an OOM. HttpError is not an IOException, so the generic "could not connect" wrapper doesn't swallow it.
    • The stream is always closed, including on error.
    • A null error stream (a 4xx/5xx with no body) now returns "" instead of throwing an NPE.
    • Removed the available() == 0 short-circuit, which could wrongly return "" for a real stream that just had no bytes buffered yet.
  • Same cap on the App Engine fallback path (makeAppEngineRequest).
  • Stripe path: the ticket also mentions the Stripe tokenization response read in ReferralCustomerService.createStripeToken. That code was already removed on master in chore: remove deprecated, unused addCreditCardToUser function #381 (addCreditCardToUser removal), so nothing is left to patch here.

No unbounded reads remain in src/main.

Release plan: no backport to 8.8.x. Both halves of SEC-787 ship together in the next release from master: the Stripe-path removal from #381 and this cap. Users get both by upgrading to that version. Note that the release includes the breaking addCreditCardToUser removal, so its version number should be chosen accordingly.

Testing

New RequestorTest (it extends Requestor to reach the protected helper, the same pattern ErrorTest uses):

  • A normal UTF-8 body, including a multi-byte character split across the 8 KB read boundary
  • Empty stream and null stream
  • A body exactly at the limit is accepted
  • A Content-Length over the cap is rejected with zero bytes read
  • A missing Content-Length with an endless stream is rejected once the cap is crossed
  • An understated Content-Length with an oversized stream is rejected
  • End-to-end through client.address.retrieve(...) with a mocked HttpsURLConnection (via EasyPost._vcrUrlFunction): a normal response deserializes, and both oversized cases throw HttpError

⚠️ I couldn't build this locally (there was no JDK/Maven in my environment), so CI is the first compile, checkstyle and test run.

Pull Request Type

Please select the option(s) that are relevant to this PR.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Improvement (fixing a typo, updating readme, renaming a variable name, etc)

🤖 Generated with Claude Code

msuitcase and others added 2 commits October 2, 2026 20:33
Requestor read entire response bodies into memory with no size limit, so
a compromised upstream, misbehaving proxy, or MITM returning a very large
body could exhaust the JVM heap.

Response bodies are now capped at 10 MB. A Content-Length above the cap is
rejected before anything is read, and the cap is also enforced while
streaming in case the header is missing or wrong. Oversized responses
throw an HttpError with a descriptive message. The App Engine fallback
path applies the same cap.

SEC-787

Co-Authored-By: Claude Opus 5.5 <[email protected]>

This branch has not been deployed

No deployments
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