fix(helpers): correct data: URL base64 size estimate for maxContentLength guard - #11061
Merged
jasonsaayman merged 4 commits intoJul 14, 2026
Merged
Conversation
…ngth guard estimateDataURLDecodedBytes is the pre-decode size guard the Node http and fetch adapters use to reject data: URLs whose decoded body exceeds config.maxContentLength before allocating a Buffer. Its base64 branch assumed the body was percent-decoded before base64 decoding: it subtracted 2 for each %XX escape and treated a trailing %3D as == padding. fromDataURI decodes the raw body with Buffer.from(body, 'base64'), which never percent-decodes. Node's decoder ignores characters outside the base64 alphabet (including a bare %) and keeps the surrounding hex digits as real base64 data, and %3D is not padding. A base64 body sprinkled with %XX (hex digits that are valid base64 chars) was therefore under-estimated by up to ~2x, letting a body larger than maxContentLength through. Rewrite the base64 branch to mirror Node's decoder: count only significant base64/base64url alphabet characters, ignore everything else, stop at the first literal =, then compute bytes from the group count and remainder. The estimate now satisfies the invariant estimate >= Buffer.from(body, 'base64').length. Also correct the existing unit oracle for TQ%3D%3D (was 1, actual decoded length is 4) and add a regression test for percent-embedded base64 bodies.
Ziiyodullayevv
left a comment
There was a problem hiding this comment.
Important fix for base64 size estimation. Correct data: URL size calculation ensures maxContentLength guard works properly for embedded content.
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The base64 branch of
estimateDataURLDecodedBytes()under-reports the actual decoded byte length, which weakens themaxContentLengthguard the Node http and fetch adapters apply todata:URLs before decoding.Steps to reproduce
fromDataURI()decodes the raw body withBuffer.from(body, 'base64'), so the estimate must never be lower than the length that call produces. It currently is:Because the estimate can be roughly half of the real decoded size, a body up to about twice
maxContentLengthpasses the check and is then fully decoded into a Buffer.Root cause
The base64 branch assumed the body was percent-decoded before base64 decoding: it subtracted 2 for every
%XXescape and treated a trailing%3Das==padding. Node's base64 decoder does neither. It never percent-decodes, it ignores any character outside the base64 alphabet (a bare%is dropped), and it keeps the surrounding hex digits as ordinary base64 data.%3Dis not padding: the%is dropped and3Ddecode as data.Fix
Rewrite the base64 branch to mirror Node's decoder: count only the significant base64/base64url alphabet characters, ignore everything else, stop at the first literal
=, then derive bytes from the group count plus the trailing remainder (2 chars -> 1 byte, 3 chars -> 2 bytes). The estimate now satisfies the invariantestimate >= Buffer.from(body, 'base64').length, so it may over-estimate but never under-report.Tests
The existing unit test for
TQ%3D%3Dasserted an incorrect oracle value of1; the real decoded length is4, and the assertion is corrected to compare againstBuffer.from(body, 'base64').length. A regression test is added for percent-embedded base64 bodies that asserts the>=invariant.npm run test:vitest:unit -- tests/unit/estimateDataURLDecodedBytes.test.js: 8/8 pass with the fix; the two updated assertions fail on the current branch without it.npm run test:vitest:unit: 948/948 pass.npx eslint lib/helpers/estimateDataURLDecodedBytes.js: clean.🏄
Summary by cubic
Fixes base64 data: URL size under-estimation so
maxContentLengthis enforced correctly in both Node HTTP andfetch. Forfetch, the size check now ignores URL fragments.Description
Summary of changes
estimateDataURLDecodedBytes()(fetch) andestimateDataURLBufferAllocation()(Node HTTP).=.lib/adapters/http.jsto useestimateDataURLBufferAllocation().Reasoning
Buffer.from(body, 'base64')never percent-decodes; old logic undercounted when%XXappeared, letting oversized bodies pass.data:URLs without fragments; estimators now mirror these behaviors.Additional context
-,_).Docs
Suggest updating
/docs/to explain:data:URL size checks differ: fetch (percent-decoded decoded size, ignores fragments) vs Node HTTP (raw base64 Buffer allocation, counts ignored chars and post-=input).Testing
maxContentLength, allows percent-encoded padding at the limit, ignores fragments.=.Semantic version impact
Patch: bug fix tightening
maxContentLengthenforcement fordata:URLs without public API changes.Written for commit 1940751. Summary will update on new commits.