|
|
@@ -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 (37 fixed, 5 open).
|
|
|
+> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (27 fixed, 0 open), 42 sev-1 (38 fixed, 4 open).
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -2241,6 +2241,31 @@
|
|
|
amplifier. Constrain `format` per consumer (only allow what the
|
|
|
consumer typically uses).
|
|
|
- **Severity: 1**
|
|
|
+- **Status:** Fixed by extending `BlocklistCache` to cache the
|
|
|
+ rendered body and its sha256 ETag per `(policy_id, format)`, not
|
|
|
+ just the underlying `Blocklist` domain object. New
|
|
|
+ `BlocklistCache::getRendered(Policy, format, callable $render)`
|
|
|
+ invokes the render callback at most once per cache window per
|
|
|
+ format; subsequent hits return the cached `body` + `etag`
|
|
|
+ verbatim. The render+hash work that previously paid the full
|
|
|
+ sha256-of-N-entries cost on every request now runs once per
|
|
|
+ cache window per replica per format, regardless of request rate.
|
|
|
+ `BlocklistController` switched to the new method and feeds it a
|
|
|
+ small closure for each format; the choice between `renderText`
|
|
|
+ and `renderJson` is the only thing varying per request now. The
|
|
|
+ per-format invalidation contract from `getOrBuild` is preserved
|
|
|
+ via the same `invalidate($policyId)` / `invalidateAll()`
|
|
|
+ surface (the rendered entries live inside the same cache row,
|
|
|
+ so dropping the row drops both the build and every render
|
|
|
+ alongside it). Per-consumer format constraint is still possible
|
|
|
+ as a future tightening but isn't necessary to defeat the F70
|
|
|
+ amplifier — the cache already does. Regression tests in
|
|
|
+ `api/tests/Integration/Public/BlocklistControllerTest.php`:
|
|
|
+ `testBlocklistRenderIsCachedAcrossRequests` (two sequential
|
|
|
+ JSON requests produce byte-identical bodies and ETags) and
|
|
|
+ `testTextAndJsonRendersBothCachedIndependently` (alternating
|
|
|
+ format requests preserve each format's cache; text and JSON
|
|
|
+ ETags differ because their bodies differ).
|
|
|
|
|
|
### F71 — `BlocklistCache` is not size-bounded
|
|
|
- **File:** `api/src/Infrastructure/Reputation/BlocklistCache.php:33, 42-59`
|