A 5-minute walkthrough: install ssrf-guard, configure a whitelist, call a real partner API, and watch a malicious URL get blocked.
```kotlin title=”build.gradle.kts” implementation(“kr.devslab:ssrf-guard:2.0.0”)
## 2. Configure the whitelist
```yaml title="application.yml"
ssrf:
guard:
suffixes:
- api.partner.com
That’s the only required setting — defaults handle scheme, port, private-network blocking, and redirect re-validation.
RestClient```java title=”PartnerApiService.java” package com.example.demo;
import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient;
@Service public class PartnerApiService {
private final RestClient client;
public PartnerApiService(RestClient.Builder builder) {
// The builder has already been customized by ssrf-guard — every
// RestClient built from it gets the SSRF policy automatically.
this.client = builder.build();
}
public String fetchCustomer(long id) {
return client.get()
.uri("https://api.partner.com/customers/{id}", id)
.retrieve()
.body(String.class);
} } ```
Same RestClient, different host:
String body = client.get()
.uri("http://169.254.169.254/latest/meta-data/")
.retrieve()
.body(String.class);
java.lang.SecurityException: Host not allowed: 169.254.169.254
at kr.devslab.ssrfguard.security.SsrfGuardInterceptor.intercept(SsrfGuardInterceptor.java:48)
The interceptor rejected it before DNS, before the connection, before anything that could go wrong did. If you somehow bypass the interceptor (a URL form the parser handles differently), SafeDnsResolver catches it at DNS time and refuses to return the loopback / link-local IP.
Add a host you control that returns 302 Location: http://169.254.169.254/...:
```yaml title=”application.yml” ssrf: guard: suffixes: - api.partner.com - your-redirect-host.com
```java
client.get()
.uri("https://your-redirect-host.com/redirect-to-metadata")
.retrieve()
.body(String.class);
org.apache.hc.client5.http.RedirectException: Blocked redirect to host: 169.254.169.254 cause: ...
SafeRedirectStrategy ran the redirect target through the same DNS resolver that blocks link-local addresses. The attacker’s bait worked exactly as well as a direct call to 169.254.169.254 — i.e., not at all.
RestClient.get(uri)
↓
SsrfGuardInterceptor ← scheme / host / port whitelist (no DNS yet)
↓
HttpComponents → SafeDnsResolver ← whitelist + private-IP filter
↓
Socket.connect(resolved-addr) ← same InetAddress array, no second lookup
↓
(on 3xx)
SafeRedirectStrategy ← run target URI through the same checks
↓
loop or terminate
Three independent gates, each closing a different bypass an attacker would try.
com.devs.lab coordinate