[← Wiring it up](05-wiring-it-up.md) · [Module README](../README.md) · [Composing filters →](07-composing-filters.md) # 6. Operating it ## What the caller sees `FilteredHostException` is a plain `RuntimeException`. Uncaught in a controller it is a bare **HTTP 500**, and Boot's default error body does not name the host — so the first symptom in production is a 500 with nothing useful in the response and a stack trace in the log. Catch it. It carries the two things you want: ```java catch (FilteredHostException ex) { log.warn("outbound call to {} blocked by {}", ex.getHost(), ex.getFilter()); return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(...); } ``` `getHost()` is the host **string from the URI**, not the resolved address — `Filtered host 'localhost'`, not `Filtered host '127.0.0.1'`. On the Apache path the resolved addresses were available and were not kept, so if you want to log which address triggered it you have to resolve again yourself. A 4xx is arguably the better answer when the URL came from a user, since the request is what is wrong. Do not echo `ex.getMessage()` back to them: it confirms which hosts are unreachable, which turns your error response into a network scanner. Log it, return something bland. ## Alert on it A `FilteredHostException` is either an attack or an outage, and you want to know which. Both are worth paging on eventually, but the second is the one that will bite you first: an allow-list pinned to IP ranges fails the day the destination changes its DNS. The `allowlist` profile in this module was written against `93.184.216.34`, which was example.com's address for a decade and is not any more. ## Testing it The filter is a `@FunctionalInterface` with no Spring dependencies, so the allow/deny decision is a unit test — no context, no network: ```java assertThat(InetAddressFilter.externalAddresses() .matches(InetAddress.getByName("169.254.169.254"))).isFalse(); ``` [`FilterMatrix`](../src/main/java/com/ankurm/ssrf/FilterMatrix.java) is that idea with a table around it. Run it against your own filter before you deploy it; the four-row disagreement in [chapter 3](03-allow-not-block.md) is not something you would find by reading. ## The diagnostic endpoint [`/diag/filter?host=...`](../src/main/java/com/ankurm/ssrf/DiagnosticsController.java) reports whether a filter bean is present, whether the settings picked it up, and the verdict on every address the host resolves to: ```json { "filterBeanPresent": true, "settingsCarryFilter": true, "host": "example.com", "resolvesTo": { "172.66.147.243": true, "104.20.23.154": true } } ``` That answers the question you actually have when an outbound call fails, which is not "what does my configuration say" but "what does the running context think". **Delete it before shipping**: it is an oracle for your outbound allow-list and a host-resolution service for anyone who finds it. [Composing filters →](07-composing-filters.md)