|
|
@@ -11,7 +11,7 @@
|
|
|
>
|
|
|
> Each finding is referenced as **F<N>** for later citation.
|
|
|
>
|
|
|
-> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (27 fixed, 0 open), 42 sev-1 (36 fixed, 6 open).
|
|
|
+> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (27 fixed, 0 open), 42 sev-1 (37 fixed, 5 open).
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -2191,6 +2191,44 @@
|
|
|
amplifies. Set `JSON_THROW_ON_ERROR`, cap body size in Caddy, and
|
|
|
enforce `php_value memory_limit`.
|
|
|
- **Severity: 1**
|
|
|
+- **Status:** Fixed at the application layer. Two new defences:
|
|
|
+ 1. **`RequestBodySizeLimitMiddleware`** under
|
|
|
+ `api/src/Infrastructure/Http/Middleware/`. Wired globally in
|
|
|
+ `AppFactory::build` AFTER `addBodyParsingMiddleware()` so
|
|
|
+ it runs FIRST on the inbound LIFO stack — meaning Slim's
|
|
|
+ body parser never reads a body that exceeds the 256 KiB cap.
|
|
|
+ Two layers of check: `Content-Length` header (catches the
|
|
|
+ well-behaved client) and `getSize()` fallback (catches a
|
|
|
+ stream that knows its length even if the header lied or was
|
|
|
+ omitted). Returns 413 `payload_too_large` JSON without
|
|
|
+ touching the stream.
|
|
|
+ 2. **`ReportController::jsonBody()` hardening.** The fall-
|
|
|
+ through path that ran when Slim's parser didn't recognise
|
|
|
+ the `Content-Type` now uses `json_decode($raw, true,
|
|
|
+ JSON_DEPTH_CAP=32, JSON_THROW_ON_ERROR)`, with
|
|
|
+ `JsonException` caught and translated to "treat as empty
|
|
|
+ body". 32 levels is plenty for legitimate metadata shapes;
|
|
|
+ PHP's 512 default left a deep-recursion amplifier in place
|
|
|
+ even after the byte cap.
|
|
|
+ Per-endpoint caps (the existing 4 KiB `metadata` limit) layer
|
|
|
+ on top, and Caddy's `request_body { max_size … }` is the
|
|
|
+ outermost bound — operators can configure that per-route in the
|
|
|
+ Caddyfile if they want to fail-closed before PHP is invoked at
|
|
|
+ all. The 256 KiB application-layer cap is generous enough for
|
|
|
+ every legitimate admin payload (largest is a multi-line
|
|
|
+ description, ≤16 KiB) while bounding the worst-case at "no
|
|
|
+ single request can fill memory by itself".
|
|
|
+ Regression tests:
|
|
|
+ - Unit: `api/tests/Unit/Http/RequestBodySizeLimitMiddlewareTest.php`
|
|
|
+ covers under-cap-passes, over-cap-by-Content-Length-rejected,
|
|
|
+ over-cap-by-stream-size-rejected (Content-Length absent),
|
|
|
+ empty-body-passes, and non-numeric-Content-Length-still-
|
|
|
+ caught-by-stream-size.
|
|
|
+ - Integration: `api/tests/Integration/Public/ReportControllerTest.php`
|
|
|
+ `testOversizedRequestBodyIsRejectedWith413` posts a 512 KiB
|
|
|
+ JSON body and asserts 413 + `payload_too_large` envelope;
|
|
|
+ `testDeeplyNestedJsonDoesNotBlowTheStack` posts a 100-deep
|
|
|
+ nested object and asserts no 500/exception leaks.
|
|
|
|
|
|
### F70 — `BlocklistController?format=json` forces sha256-of-50k-entries on every cache miss
|
|
|
- **File:** `api/src/Application/Public/BlocklistController.php:80-85, 176-193`
|