From 55c5f86a85e174fa21b38b5fdedf2375b350c951 Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Sat, 23 May 2026 15:12:05 +0900 Subject: [PATCH] =?UTF-8?q?chore(ssrf-guard=20demos):=20bump=203.0.0=20?= =?UTF-8?q?=E2=86=92=203.0.1=20+=20drop=20micrometer-core=20workaround?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssrf-guard 3.0.1 gates the Micrometer-backed metrics bean by @ConditionalOnClass(MeterRegistry.class), so the autoconfig boots cleanly without micrometer-core on the classpath. The workaround dep added to the feign / jdkhttp / okhttp demos can now come out — the libraries fall back to NoOpSsrfGuardMetrics on their own. Touched: - All 5 demo build.gradle.kts files → :3.0.1 - feign demo build.gradle.kts → remove `io.micrometer:micrometer-core` - 5 demo READMEs → version-string mentions - Top-level README.md → Maven Central matrix versions Verified locally — every demo built with --refresh-dependencies against the freshly-published Maven Central artifacts. All smoke tests green. --- README.md | 10 +++++----- ssrf-guard-demo/README.md | 8 ++++---- ssrf-guard-demo/build.gradle.kts | 6 +++--- ssrf-guard-feign-demo/README.md | 4 ++-- ssrf-guard-feign-demo/build.gradle.kts | 8 +------- ssrf-guard-jdkhttp-demo/README.md | 2 +- ssrf-guard-jdkhttp-demo/build.gradle.kts | 2 +- ssrf-guard-okhttp-demo/README.md | 2 +- ssrf-guard-okhttp-demo/build.gradle.kts | 2 +- ssrf-guard-springai-demo/README.md | 2 +- ssrf-guard-springai-demo/build.gradle.kts | 4 ++-- 11 files changed, 22 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index a7adbe4..a6ba229 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Each subdirectory is an **independent** Spring Boot application with its own Gra | [`easy-paging-keyset-demo`](easy-paging-keyset-demo/) | Cursor (keyset) pagination with `@KeysetPaginate` — composite `(time, id)` key, stable under writes, no `OFFSET`/`COUNT(*)` | [`kr.devslab:easy-paging-spring-boot-starter:0.4.0`](https://central.sonatype.com/artifact/kr.devslab/easy-paging-spring-boot-starter) | | [`easy-paging-postgres-demo`](easy-paging-postgres-demo/) | Same starter against **real PostgreSQL** — Docker Compose for `bootRun`, Testcontainers + `@ServiceConnection` for tests, no local DB install | [`kr.devslab:easy-paging-spring-boot-starter:0.4.0`](https://central.sonatype.com/artifact/kr.devslab/easy-paging-spring-boot-starter) | | [`easy-paging-reactive-demo`](easy-paging-reactive-demo/) | Reactive stack — **WebFlux + R2DBC** via `R2dbcOffsetPagingSupport`. Same JSON envelope as the MVC demos, served as `Mono>` | [`kr.devslab:easy-paging-spring-boot-starter-reactive:0.4.0`](https://central.sonatype.com/artifact/kr.devslab/easy-paging-spring-boot-starter-reactive) | -| [`ssrf-guard-demo`](ssrf-guard-demo/) | SSRF (Server-Side Request Forgery) protection across three Spring HTTP clients (RestClient, RestTemplate, WebClient) — same `UrlPolicy` for all. 15-pattern attack matrix endpoint, Micrometer metrics. | [`kr.devslab:ssrf-guard:3.0.0`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard) | -| [`ssrf-guard-springai-demo`](ssrf-guard-springai-demo/) | ⭐ **LLM agent SSRF defense.** Wraps every Spring AI `ToolCallback` so URL-shaped tool arguments are validated before the LLM-driven `fetch_url` runs. Fake-LLM driver makes the demo runnable offline (no API key). | [`kr.devslab:ssrf-guard-springai:3.0.0`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-springai) | -| [`ssrf-guard-feign-demo`](ssrf-guard-feign-demo/) | Spring Cloud OpenFeign `RequestInterceptor` — same `UrlPolicy` applied to `@FeignClient` calls. Two `@FeignClient` interfaces (one whitelisted, one not) to show the block path. | [`kr.devslab:ssrf-guard-feign:3.0.0`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-feign) | -| [`ssrf-guard-jdkhttp-demo`](ssrf-guard-jdkhttp-demo/) | `java.net.http.HttpClient` (Java 11+) wrapper — no Spring required by the library. Three-line wiring in `main()`. | [`kr.devslab:ssrf-guard-jdkhttp:3.0.0`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-jdkhttp) | -| [`ssrf-guard-okhttp-demo`](ssrf-guard-okhttp-demo/) | OkHttp `Interceptor` + `Dns` integration — also no Spring needed. Three-line wiring on `OkHttpClient.Builder`. | [`kr.devslab:ssrf-guard-okhttp:3.0.0`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-okhttp) | +| [`ssrf-guard-demo`](ssrf-guard-demo/) | SSRF (Server-Side Request Forgery) protection across three Spring HTTP clients (RestClient, RestTemplate, WebClient) — same `UrlPolicy` for all. 15-pattern attack matrix endpoint, Micrometer metrics. | [`kr.devslab:ssrf-guard:3.0.1`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard) | +| [`ssrf-guard-springai-demo`](ssrf-guard-springai-demo/) | ⭐ **LLM agent SSRF defense.** Wraps every Spring AI `ToolCallback` so URL-shaped tool arguments are validated before the LLM-driven `fetch_url` runs. Fake-LLM driver makes the demo runnable offline (no API key). | [`kr.devslab:ssrf-guard-springai:3.0.1`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-springai) | +| [`ssrf-guard-feign-demo`](ssrf-guard-feign-demo/) | Spring Cloud OpenFeign `RequestInterceptor` — same `UrlPolicy` applied to `@FeignClient` calls. Two `@FeignClient` interfaces (one whitelisted, one not) to show the block path. | [`kr.devslab:ssrf-guard-feign:3.0.1`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-feign) | +| [`ssrf-guard-jdkhttp-demo`](ssrf-guard-jdkhttp-demo/) | `java.net.http.HttpClient` (Java 11+) wrapper — no Spring required by the library. Three-line wiring in `main()`. | [`kr.devslab:ssrf-guard-jdkhttp:3.0.1`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-jdkhttp) | +| [`ssrf-guard-okhttp-demo`](ssrf-guard-okhttp-demo/) | OkHttp `Interceptor` + `Dns` integration — also no Spring needed. Three-line wiring on `OkHttpClient.Builder`. | [`kr.devslab:ssrf-guard-okhttp:3.0.1`](https://central.sonatype.com/artifact/kr.devslab/ssrf-guard-okhttp) | ## Conventions diff --git a/ssrf-guard-demo/README.md b/ssrf-guard-demo/README.md index 86e127f..c77bacc 100644 --- a/ssrf-guard-demo/README.md +++ b/ssrf-guard-demo/README.md @@ -4,9 +4,9 @@ Runnable example for [`ssrf-guard`](https://github.com/devslab-kr/ssrf-guard) One Spring Boot app shows **all three Spring HTTP clients** wired through the same `UrlPolicy`: -- `RestClient` (Spring 6.1+) via the meta `kr.devslab:ssrf-guard:3.0.0` artifact -- `RestTemplate` via `kr.devslab:ssrf-guard-resttemplate:3.0.0` -- `WebClient` (WebFlux) via `kr.devslab:ssrf-guard-webclient:3.0.0` +- `RestClient` (Spring 6.1+) via the meta `kr.devslab:ssrf-guard:3.0.1` artifact +- `RestTemplate` via `kr.devslab:ssrf-guard-resttemplate:3.0.1` +- `WebClient` (WebFlux) via `kr.devslab:ssrf-guard-webclient:3.0.1` Plus a `/attacks` endpoint that lists every SSRF bypass pattern the guard catches, with copy-paste curls for each. @@ -123,7 +123,7 @@ You'll see counters per `reason` tag (`blocked_host`, `blocked_ip_literal`, `blo | File | Why | | --- | --- | -| `build.gradle.kts` | The only dependencies beyond the standard starters are `kr.devslab:ssrf-guard:3.0.0`, `kr.devslab:ssrf-guard-resttemplate:3.0.0`, `kr.devslab:ssrf-guard-webclient:3.0.0` — no manual configuration class needed | +| `build.gradle.kts` | The only dependencies beyond the standard starters are `kr.devslab:ssrf-guard:3.0.1`, `kr.devslab:ssrf-guard-resttemplate:3.0.1`, `kr.devslab:ssrf-guard-webclient:3.0.1` — no manual configuration class needed | | `application.yml` | Every `ssrf.guard.*` knob in one place with comments | | `web/FetchController.java` | The whole RestClient story — three lines of setup, the guard runs invisibly | | `web/FetchResttemplateController.java` | Same shape for RestTemplate — no migration needed for legacy code | diff --git a/ssrf-guard-demo/build.gradle.kts b/ssrf-guard-demo/build.gradle.kts index 62513cf..68a3d1b 100644 --- a/ssrf-guard-demo/build.gradle.kts +++ b/ssrf-guard-demo/build.gradle.kts @@ -26,9 +26,9 @@ dependencies { // The meta `ssrf-guard` artifact transitively pulls in `-core`, `-httpclient5`, // and `-restclient`. The `-resttemplate` and `-webclient` modules are // additive and reuse the same UrlPolicy / SsrfGuardMetrics beans. - implementation("kr.devslab:ssrf-guard:3.0.0") - implementation("kr.devslab:ssrf-guard-resttemplate:3.0.0") - implementation("kr.devslab:ssrf-guard-webclient:3.0.0") + implementation("kr.devslab:ssrf-guard:3.0.1") + implementation("kr.devslab:ssrf-guard-resttemplate:3.0.1") + implementation("kr.devslab:ssrf-guard-webclient:3.0.1") // Micrometer Prometheus registry — turns SSRF Guard's counters into // /actuator/prometheus output so you can curl the metrics in the demo. diff --git a/ssrf-guard-feign-demo/README.md b/ssrf-guard-feign-demo/README.md index 0229352..9f0fda9 100644 --- a/ssrf-guard-feign-demo/README.md +++ b/ssrf-guard-feign-demo/README.md @@ -28,12 +28,12 @@ curl http://localhost:8080/feign/evil | jq | File | Why | | --- | --- | -| `build.gradle.kts` | `kr.devslab:ssrf-guard-feign:3.0.0` + `spring-cloud-starter-openfeign` | +| `build.gradle.kts` | `kr.devslab:ssrf-guard-feign:3.0.1` + `spring-cloud-starter-openfeign` | | `HttpBinClient.java` / `EvilClient.java` | Two normal `@FeignClient` interfaces — no guard code | | `FeignDemoController.java` | Catches `SsrfGuardException` (wrapped one level deep by Feign — the controller unwraps) | | `application.yml` | `ssrf.guard.exact-hosts: [httpbin.org]` — that one line is the whitelist | -The Feign interceptor registers itself automatically — `ssrf-guard-feign-3.0.0` provides a Spring autoconfig that publishes a `feign.RequestInterceptor` bean, which Spring Cloud OpenFeign then applies to every `@FeignClient`. +The Feign interceptor registers itself automatically — `ssrf-guard-feign-3.0.1` provides a Spring autoconfig that publishes a `feign.RequestInterceptor` bean, which Spring Cloud OpenFeign then applies to every `@FeignClient`. ## Verify the build diff --git a/ssrf-guard-feign-demo/build.gradle.kts b/ssrf-guard-feign-demo/build.gradle.kts index 0b9e96d..788cdd6 100644 --- a/ssrf-guard-feign-demo/build.gradle.kts +++ b/ssrf-guard-feign-demo/build.gradle.kts @@ -26,13 +26,7 @@ dependencies { implementation("org.springframework.cloud:spring-cloud-starter-openfeign") // The library this demo showcases. Pulls in ssrf-guard-core transitively. - implementation("kr.devslab:ssrf-guard-feign:3.0.0") - - // ssrf-guard 3.0.0 autoconfig references MeterRegistry by type, so we - // need micrometer-core on the classpath even though we don't use metrics - // in the demo. Fixed in ssrf-guard 3.0.1 by gating the metrics bean - // behind @ConditionalOnClass(MeterRegistry.class). - implementation("io.micrometer:micrometer-core") + implementation("kr.devslab:ssrf-guard-feign:3.0.1") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/ssrf-guard-jdkhttp-demo/README.md b/ssrf-guard-jdkhttp-demo/README.md index 4392946..d46c853 100644 --- a/ssrf-guard-jdkhttp-demo/README.md +++ b/ssrf-guard-jdkhttp-demo/README.md @@ -31,7 +31,7 @@ curl 'http://localhost:8080/fetch?url=https://evil.com/' | jq | File | Why | | --- | --- | -| `build.gradle.kts` | One dep: `kr.devslab:ssrf-guard-jdkhttp:3.0.0` | +| `build.gradle.kts` | One dep: `kr.devslab:ssrf-guard-jdkhttp:3.0.1` | | `SsrfGuardJdkHttpDemoApplication.java` | The whole story: build `HostPolicy` → `UrlPolicy` → wrap `HttpClient` | | `JdkHttpDemoController.java` | Calls `client.send(req, ...)` like any other HttpClient — the wrap is invisible at the call site | diff --git a/ssrf-guard-jdkhttp-demo/build.gradle.kts b/ssrf-guard-jdkhttp-demo/build.gradle.kts index 96d95b3..deda588 100644 --- a/ssrf-guard-jdkhttp-demo/build.gradle.kts +++ b/ssrf-guard-jdkhttp-demo/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { // dependency itself; the Spring Boot framing is just the demo's UX. implementation("org.springframework.boot:spring-boot-starter-web") - implementation("kr.devslab:ssrf-guard-jdkhttp:3.0.0") + implementation("kr.devslab:ssrf-guard-jdkhttp:3.0.1") // ssrf-guard-core's @ConfigurationProperties pulls in spring-boot // (transitively from -jdkhttp's API), so we get the SsrfGuardProperties // binding for free. diff --git a/ssrf-guard-okhttp-demo/README.md b/ssrf-guard-okhttp-demo/README.md index fa11c68..3388e1c 100644 --- a/ssrf-guard-okhttp-demo/README.md +++ b/ssrf-guard-okhttp-demo/README.md @@ -24,7 +24,7 @@ curl 'http://localhost:8080/fetch?url=https://evil.com/' | jq | File | Why | | --- | --- | -| `build.gradle.kts` | `kr.devslab:ssrf-guard-okhttp:3.0.0` + `com.squareup.okhttp3:okhttp:4.12.0` | +| `build.gradle.kts` | `kr.devslab:ssrf-guard-okhttp:3.0.1` + `com.squareup.okhttp3:okhttp:4.12.0` | | `SsrfGuardOkHttpDemoApplication.java` | Three lines on the OkHttp builder — `.addInterceptor(...)`, `.dns(...)`, `.followRedirects(...)` | | `OkHttpDemoController.java` | Standard OkHttp `newCall().execute()` — the wrap is invisible at the call site | diff --git a/ssrf-guard-okhttp-demo/build.gradle.kts b/ssrf-guard-okhttp-demo/build.gradle.kts index 603e7d8..b238d6c 100644 --- a/ssrf-guard-okhttp-demo/build.gradle.kts +++ b/ssrf-guard-okhttp-demo/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { // (ssrf-guard-okhttp) has no Spring dependency. implementation("org.springframework.boot:spring-boot-starter-web") - implementation("kr.devslab:ssrf-guard-okhttp:3.0.0") + implementation("kr.devslab:ssrf-guard-okhttp:3.0.1") implementation("com.squareup.okhttp3:okhttp:4.12.0") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/ssrf-guard-springai-demo/README.md b/ssrf-guard-springai-demo/README.md index 71e705e..dc212e3 100644 --- a/ssrf-guard-springai-demo/README.md +++ b/ssrf-guard-springai-demo/README.md @@ -89,7 +89,7 @@ curl -X POST http://localhost:8080/agent/chat \ | File | Why | | --- | --- | -| `build.gradle.kts` | The dependencies — `kr.devslab:ssrf-guard-springai:3.0.0` + `org.springframework.ai:spring-ai-model:1.0.7`. That's it | +| `build.gradle.kts` | The dependencies — `kr.devslab:ssrf-guard-springai:3.0.1` + `org.springframework.ai:spring-ai-model:1.0.7`. That's it | | `application.yml` | `ssrf.guard.springai.wrap-tool-callbacks=true` — the master switch (default true, shown for clarity) | | `agent/FetchUrlTool.java` | The raw tool — note there's **zero** security code here. The wrap happens at bean post-processing time | | `agent/FakeLlmService.java` | The fake-LLM driver. In production this is a `ChatClient`. Swap, recompile, done | diff --git a/ssrf-guard-springai-demo/build.gradle.kts b/ssrf-guard-springai-demo/build.gradle.kts index 0f39594..83f12bf 100644 --- a/ssrf-guard-springai-demo/build.gradle.kts +++ b/ssrf-guard-springai-demo/build.gradle.kts @@ -25,8 +25,8 @@ dependencies { // the RestClient against the wrapped tool's "remote" target. // - ssrf-guard-springai: wraps every ToolCallback bean automatically via // a BeanPostProcessor — that's the whole "secure-by-default" pitch. - implementation("kr.devslab:ssrf-guard:3.0.0") - implementation("kr.devslab:ssrf-guard-springai:3.0.0") + implementation("kr.devslab:ssrf-guard:3.0.1") + implementation("kr.devslab:ssrf-guard-springai:3.0.1") // Spring AI 1.0 GA. We don't actually call an LLM in this demo — the // FakeLlmService stands in for one — but we pull the API in so the