فهرست منبع

docs: mark SEC_REVIEW F69 as fixed in de84fd9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 3 روز پیش
والد
کامیت
5d3f05045f
1فایلهای تغییر یافته به همراه39 افزوده شده و 1 حذف شده
  1. 39 1
      doc/SEC_REVIEW.md

+ 39 - 1
doc/SEC_REVIEW.md

@@ -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`