Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<PageResponse<T>>` | [`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

Expand Down
8 changes: 4 additions & 4 deletions ssrf-guard-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 |
Expand Down
6 changes: 3 additions & 3 deletions ssrf-guard-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions ssrf-guard-feign-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 1 addition & 7 deletions ssrf-guard-feign-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion ssrf-guard-jdkhttp-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
2 changes: 1 addition & 1 deletion ssrf-guard-jdkhttp-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion ssrf-guard-okhttp-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
2 changes: 1 addition & 1 deletion ssrf-guard-okhttp-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion ssrf-guard-springai-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
4 changes: 2 additions & 2 deletions ssrf-guard-springai-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading