|
@@ -11,7 +11,7 @@
|
|
|
>
|
|
>
|
|
|
> Each finding is referenced as **F<N>** for later citation.
|
|
> 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 (17 fixed, 25 open).
|
|
|
|
|
|
|
+> **Findings rolled up:** 5 sev-3 (5 fixed, 0 open), 27 sev-2 (27 fixed, 0 open), 42 sev-1 (18 fixed, 24 open).
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -1646,6 +1646,38 @@
|
|
|
`allow_redirects => ['max'=>3,'protocols'=>['https'],'strict'=>true,'referer'=>false]`
|
|
`allow_redirects => ['max'=>3,'protocols'=>['https'],'strict'=>true,'referer'=>false]`
|
|
|
plus a private-IP guard.
|
|
plus a private-IP guard.
|
|
|
- **Severity: 1**
|
|
- **Severity: 1**
|
|
|
|
|
+- **Status:** Fixed. Two layers added to the GeoIP-downloader Guzzle
|
|
|
|
|
+ client in `api/src/App/Container.php`:
|
|
|
|
|
+ 1. **Tight `allow_redirects`.** `['max' => 3, 'protocols' =>
|
|
|
|
|
+ ['https'], 'strict' => true, 'referer' => false,
|
|
|
|
|
+ 'track_redirects' => false]`. Caps the chain at 3 hops, refuses
|
|
|
|
|
+ to follow `http://` or `file://` redirects, and never sends a
|
|
|
|
|
+ Referer.
|
|
|
|
|
+ 2. **`PrivateHostGuardMiddleware`.** New handler-stack middleware
|
|
|
|
|
+ in `api/src/Infrastructure/Enrichment/Downloaders/`. Inspects
|
|
|
|
|
+ the URL's literal host on every outgoing request — including
|
|
|
|
|
+ each redirect target — and throws
|
|
|
|
|
+ `GuzzleHttp\Exception\TransferException` before opening a socket
|
|
|
|
|
+ to a loopback / link-local / RFC1918 / CGNAT / multicast
|
|
|
|
|
+ address (IPv4 + IPv6) or a known instance-metadata hostname
|
|
|
|
|
+ (`169.254.169.254`, `metadata.google.internal`, `localhost`,
|
|
|
|
|
+ `0.0.0.0`). The post-redirect
|
|
|
|
|
+ `http://169.254.169.254/...` / `https://localhost/...` /
|
|
|
|
|
+ `https://10.0.0.1/...` patterns therefore die at the request
|
|
|
|
|
+ layer rather than reaching the network. The guard inspects
|
|
|
|
|
+ literal hosts only — pinning DNS to catch a public hostname
|
|
|
|
|
+ pointing at a private IP is out of scope for "defence in depth";
|
|
|
|
|
+ the primary controls are the constant base URL plus the
|
|
|
|
|
+ `protocols => ['https']` redirect gate.
|
|
|
|
|
+ Regression tests in
|
|
|
|
|
+ `api/tests/Unit/Enrichment/PrivateHostGuardMiddlewareTest.php`:
|
|
|
|
|
+ 17 blocked-host data-provider cases (IPv4 + IPv6 across loopback,
|
|
|
|
|
+ link-local, RFC1918, CGNAT, multicast, all-zero, metadata IP,
|
|
|
|
|
+ metadata hostname, `localhost`), 6 allowed-host cases (`download.
|
|
|
|
|
+ db-ip.com`, `download.maxmind.com`, `ipinfo.io`, just-outside-
|
|
|
|
|
+ 172.16/12, `1.1.1.1`, public IPv6), `testFactoryProducesMiddleware
|
|
|
|
|
+ ThatGuardsBeforeHandler` proving the inner handler is not
|
|
|
|
|
+ invoked on the blocked branch, and `testEmptyHostIsRejected`.
|
|
|
|
|
|
|
|
### F51 — `RoleMappingRepository` placeholder generation does not enforce list shape
|
|
### F51 — `RoleMappingRepository` placeholder generation does not enforce list shape
|
|
|
- **File:** `api/src/Infrastructure/Auth/RoleMappingRepository.php:31-36`
|
|
- **File:** `api/src/Infrastructure/Auth/RoleMappingRepository.php:31-36`
|