All notable changes to ssrf-guard are recorded here.
The format follows Keep a Changelog and the project adheres to Semantic Versioning.
ClassNotFoundException: io.micrometer.core.instrument.MeterRegistry when consumers don’t have micrometer-core on the classpath. Affected every Spring autoconfig module — -restclient, -resttemplate (via -restclient), -webclient, -feign, -springai — because the metrics bean factory method declared ObjectProvider<MeterRegistry> as a parameter, and the JVM resolves parameter types at class load time even when ObjectProvider would otherwise handle the missing bean gracefully.@Configuration class gated by @ConditionalOnClass(name = "io.micrometer.core.instrument.MeterRegistry") (string form, so Spring’s ASM-based condition evaluator inspects the annotation without JVM-loading Micrometer). The outer autoconfig declares a fallback @Bean returning NoOpSsrfGuardMetrics under @ConditionalOnMissingBean(SsrfGuardMetrics.class).Drop in v3.0.1 — no consumer code changes. If you previously worked around the v3.0.0 bug by adding io.micrometer:micrometer-core to your build only because of this error (not because you actually wanted metrics), the dep can come out now.
The v2.0.0 starter was a single jar that only worked with Spring’s RestClient. v3.0.0 splits the codebase along client boundaries, adds support for every common JVM HTTP stack, and ships a Spring AI Tool wrapper that closes the SSRF surface LLM agents have been introducing for the last two years.
| Module | Use case |
|---|---|
ssrf-guard-core |
Policy / NetUtil / Micrometer metrics interface — no Spring dependency |
ssrf-guard-httpclient5 |
Apache HttpClient 5 DnsResolver + RedirectStrategy (TOCTOU closure) |
ssrf-guard-restclient |
Spring 6.1+ RestClient autoconfig (the v2.0.0 surface, now its own module) |
ssrf-guard-resttemplate |
NEW — Spring RestTemplate autoconfig for the enterprise/legacy crowd |
ssrf-guard-webclient |
NEW — Spring WebFlux WebClient ExchangeFilterFunction + autoconfig |
ssrf-guard-feign |
NEW — Spring Cloud OpenFeign RequestInterceptor + autoconfig |
ssrf-guard-springai |
NEW — Spring AI ToolCallback wrapper that validates URL-shaped tool arguments before LLM-driven execution. The hot SSRF surface in 2025+ |
ssrf-guard-jdkhttp |
NEW — java.net.http.HttpClient wrapper (no Spring, JDK 11+) |
ssrf-guard-okhttp |
NEW — OkHttp Interceptor + Dns (no Spring) |
ssrf-guard |
Meta artifact — bundles -core + -httpclient5 + -restclient for v2.0.0 back-compat |
ssrf.guard.reject-ip-literal-hosts=true default). Any URL whose host parses as an IP literal in any form — dotted decimal (127.0.0.1), bare decimal (2130706433), hex (0x7f000001), octal (0177.0.0.1), partial (127.1), IPv6 ([::1]) — is rejected at the URL-time check, before DNS. Closes the obfuscated-IP bypass class.ssrf.guard.reject-user-info=true default). URLs of the form https://user:pass@host/... are rejected — known SSRF bypass vector and credential-leak risk.::ffff:10.0.0.5 and 2002:0a00:: (the 6to4 form wrapping 10.0.0.0/8) are now correctly classified as private, not “public IPv6 that happens to embed an internal v4”. Java’s isLoopbackAddress() misses these.MeterRegistry bean is on the classpath:
ssrf_guard_blocked_total{reason="blocked_private_ip", scheme="http"} 42
ssrf_guard_allowed_total{scheme="https"} 13042
ssrf-guard: <message> (reason=blocked_private_ip, scheme=http, host=169.254.169.254).Package renames. Types moved out of the catch-all kr.devslab.ssrfguard.security package into their respective modules. The ssrf-guard meta artifact re-exports them, so import kr.devslab.ssrfguard.* consumers may need to update imports:
| v2.0.0 | v3.0.0 |
|---|---|
kr.devslab.ssrfguard.autoconfigure.SsrfGuardAutoConfiguration |
kr.devslab.ssrfguard.restclient.SsrfGuardRestClientAutoConfiguration |
kr.devslab.ssrfguard.autoconfigure.SsrfGuardProperties |
kr.devslab.ssrfguard.core.SsrfGuardProperties |
kr.devslab.ssrfguard.security.SsrfGuardInterceptor |
kr.devslab.ssrfguard.restclient.SsrfGuardClientHttpRequestInterceptor |
kr.devslab.ssrfguard.security.SafeDnsResolver |
kr.devslab.ssrfguard.httpclient5.SafeDnsResolver |
kr.devslab.ssrfguard.security.SafeRedirectStrategy |
kr.devslab.ssrfguard.httpclient5.SafeRedirectStrategy |
kr.devslab.ssrfguard.security.NetUtil |
kr.devslab.ssrfguard.core.NetUtil |
SecurityException → SsrfGuardException. All rejection paths now throw SsrfGuardException (still a subclass of SecurityException, so v2.0.0 catch blocks keep working). The exception carries a BlockReason enum tag for metrics / logging.ssrf.guard.reject-ip-literal-hosts and ssrf.guard.reject-user-info default to true — turning them off restores v2.0.0 behaviour on those two checks.For most consumers, update the version and rebuild — that’s it:
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard</artifactId>
<version>3.0.0</version>
</dependency>
The ssrf-guard meta artifact transitively pulls in -core, -httpclient5, and -restclient, which together provide the entire v2.0.0 surface.
If you use a different HTTP client, pick the matching module:
<!-- RestTemplate -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-resttemplate</artifactId>
<version>3.0.0</version>
</dependency>
<!-- WebClient -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-webclient</artifactId>
<version>3.0.0</version>
</dependency>
<!-- Spring AI tool calls -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-springai</artifactId>
<version>3.0.0</version>
</dependency>
If your code catches SecurityException from outbound calls, it still works — SsrfGuardException extends SecurityException. If you want the structured tag, catch SsrfGuardException and inspect e.reason().
kr.devslab:ssrf-guardcom.devs.lab:ssrf-guard-spring-boot-starter to kr.devslab:ssrf-guard. The legacy artifact was never published to Maven Central, so v2.0.0 is the first proper Central release.BREAKING — package renamed. devs.lab.ssrf.* → kr.devslab.ssrfguard.*:
| Old | New |
|---|---|
devs.lab.ssrf.config.SsrfGuardAutoConfiguration |
kr.devslab.ssrfguard.autoconfigure.SsrfGuardAutoConfiguration |
devs.lab.ssrf.security.SsrfGuardProperties |
kr.devslab.ssrfguard.autoconfigure.SsrfGuardProperties |
devs.lab.ssrf.security.SsrfGuardInterceptor |
kr.devslab.ssrfguard.security.SsrfGuardInterceptor |
devs.lab.ssrf.security.SafeDnsResolver |
kr.devslab.ssrfguard.security.SafeDnsResolver |
devs.lab.ssrf.security.SafeRedirectStrategy |
kr.devslab.ssrfguard.security.SafeRedirectStrategy |
devs.lab.ssrf.security.NetUtil |
kr.devslab.ssrfguard.security.NetUtil |
SsrfGuardApplication removed. The empty @SpringBootApplication was vestigial scaffolding from the original Spring Initializr template; a starter library has no business carrying a main class.v[0-9]+.[0-9]+.[0-9]+ runs the release workflow, which builds + signs + uploads to Sonatype Central Portal and creates a GitHub Release in one step..github/workflows/ci.yml) — runs ./gradlew build jacocoTestReport on every push to main and on every PR, uploads coverage to Codecov.README.md / README.ko.md).NetUtilTest — whitelist matching (exact + suffix), IDN normalisation, private-IP classification across IPv4 (loopback, RFC-1918, link-local incl. AWS metadata, CGNAT, benchmark, broadcast) and IPv6 (ULA, link-local).SafeDnsResolverTest — whitelist gate + private-IP filter, including the “filtered everything” path.SsrfGuardInterceptorTest — scheme/host/port accept/reject matrix, suffix label-boundary lookalike (the classic badexample.com bypass).SsrfGuardAutoConfigurationTest — every public bean of the auto-config is registered when enabled, and none are when ssrf.guard.enabled=false.SsrfGuardIntegrationTest — real HTTP through MockWebServer, end-to-end through the four-layer defense.Update your dependency coordinate and any direct imports:
<!-- v1.x (never on Maven Central) -->
<dependency>
<groupId>com.devs.lab</groupId>
<artifactId>ssrf-guard-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<!-- v2.0.0 -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard</artifactId>
<version>2.0.0</version>
</dependency>
application.yml keys are unchanged — ssrf.guard.* works identically.
Direct imports of the security types? Replace devs.lab.ssrf with kr.devslab.ssrfguard and split between kr.devslab.ssrfguard.autoconfigure (properties + auto-config) and kr.devslab.ssrfguard.security (interceptor + resolver + redirect + NetUtil).
semantic-release rollup of pre-v2 work. Tagged but never published to Maven Central.
Initial public release. Tagged but never published to Maven Central.
com.devs.lab:ssrf-guard-spring-boot-starter