feat: cap the discovery document and JWKS at 1 MiB - #60
Merged
Conversation
Both documents were decoded and cached with no size limit, so a hostile or misconfigured endpoint could hand over an unbounded body. Both are a few kilobytes in practice, so a mebibyte is generous. The declared body size is checked first where the response reports one, and the retrieved content unconditionally, because a chunked response reports no size. Both resources go through fetchJsonResource(), so one cap covers them. The cap bounds what gets decoded and written to the cache, not peak memory: Guzzle has already buffered the body by the time it is visible here. Bounding the transfer itself would need a streaming read against a `stream => true` request, which is a larger change than the exposure warrants for a document fetched from a configured host over TLS. That limitation is stated in the method docblock rather than left implied. The boundary is tested from both sides — exactly at the limit is accepted, one byte over is refused — for the declared size and for the content, which is also what pins the constant against a mutation off by one.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #60 +/- ##
===========================================
Coverage 100.00% 100.00%
- Complexity 83 87 +4
===========================================
Files 1 1
Lines 208 220 +12
===========================================
+ Hits 208 220 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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 discovery document and the JWKS were decoded and cached with no size limit, so a hostile or misconfigured endpoint could hand this library an unbounded body. Both are a few kilobytes in practice, so a mebibyte is generous.
Changes
fetchJsonResource()gains aMAX_JSON_RESOURCE_BYTEScap of 1 MiB and raisesHttpExceptionabove it. Both documents are fetched through that one method, so a single cap covers the discovery document and the JWKS together.Two checks, because one is not enough:
What the cap does and does not do
It bounds what gets
json_decoded and written to the PSR-6 cache. It does not bound peak memory: Guzzle has already buffered the whole body by the time this library sees it. Bounding the transfer itself would mean a streaming read against astream => truerequest, which is a larger change than the exposure warrants for a document fetched from a configured host over TLS. That limitation is written into the method docblock rather than left for the next reader to discover.Verification
134 tests, all green. Coverage 100% (30/30 methods, 191/191 lines). Mutation: 239 generated, 235 killed, 4 ignored, zero escaped, MSI 100%.
The boundary is tested from both sides — exactly at the limit accepted, one byte over refused — for the declared size and for the content. That pairing is what pins the constant: without an exact-limit test, a mutation shifting 1048576 by one survives.
PHPStan max clean at the ceiling and at the dependency floor, php-cs-fixer, markdownlint, prettier and
composer normalizeall clean.