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
8 changes: 5 additions & 3 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

전체 인덱스: [github.com/devslab-kr/devslab-examples](https://github.com/devslab-kr/devslab-examples).

## 모듈 매트릭스 (v3.0.0)
## 모듈 매트릭스

쓰는 HTTP 클라이언트에 맞는 모듈만 고르세요. `ssrf-guard-core`는 transitive로 따라옴.

Expand All @@ -38,9 +38,11 @@
| **`ssrf-guard`** | 메타 — RestClient + HttpClient5 (v2.0.0 호환) | ✅ |
| `ssrf-guard-restclient` | Spring 6.1+ `RestClient` | ✅ |
| `ssrf-guard-resttemplate` | Spring `RestTemplate` | ✅ |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` | ✅ |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` — URL 단계 필터 + reactor-netty DNS 단계 IP 필터 (v3.1+) | ✅ |
| `ssrf-guard-feign` | Spring Cloud OpenFeign | ✅ |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` URL 검증 — LLM 에이전트 SSRF 차단 | ✅ |
| `ssrf-guard-llm` 🧩 | 프레임워크-중립 JSON 툴 입력 검증 (v3.1+) — LLM 어댑터들이 재사용 | — |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` URL 검증 — `-llm` 위의 thin adapter | ✅ |
| **`ssrf-guard-langchain4j`** ⭐ | LangChain4j `ToolExecutor` URL 검증 — Java LLM 양대 프레임워크 다른 한쪽 (v3.1+) | ✅ |
| `ssrf-guard-httpclient5` | Apache HttpClient 5 직접 | — |
| `ssrf-guard-jdkhttp` | `java.net.http.HttpClient` | — |
| `ssrf-guard-okhttp` | OkHttp | — |
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Standalone Spring Boot projects that exercise every module documented below —

Full index at [github.com/devslab-kr/devslab-examples](https://github.com/devslab-kr/devslab-examples).

## Module matrix (v3.0.0)
## Module matrix

Pick the module matching your HTTP client. The core (`ssrf-guard-core`) follows transitively.

Expand All @@ -38,9 +38,11 @@ Pick the module matching your HTTP client. The core (`ssrf-guard-core`) follows
| **`ssrf-guard`** | Meta artifact — RestClient + HttpClient5 (v2.0.0 back-compat) | ✅ |
| `ssrf-guard-restclient` | Spring 6.1+ `RestClient` | ✅ |
| `ssrf-guard-resttemplate` | Spring `RestTemplate` | ✅ |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` | ✅ |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` — URL-time filter + reactor-netty DNS-time IP filter (v3.1+) | ✅ |
| `ssrf-guard-feign` | Spring Cloud OpenFeign | ✅ |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` URL validation — closes the LLM-agent SSRF surface | ✅ |
| `ssrf-guard-llm` 🧩 | Framework-agnostic JSON tool-input validator (v3.1+) — reused by the LLM adapters | — |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` URL validation — thin adapter over `-llm` | ✅ |
| **`ssrf-guard-langchain4j`** ⭐ | LangChain4j `ToolExecutor` URL validation — same defense for the other Java LLM framework (v3.1+) | ✅ |
| `ssrf-guard-httpclient5` | Apache HttpClient 5 directly | — |
| `ssrf-guard-jdkhttp` | `java.net.http.HttpClient` | — |
| `ssrf-guard-okhttp` | OkHttp | — |
Expand Down
33 changes: 32 additions & 1 deletion docs/getting-started/installation.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,38 @@ ssrf-guard v3.0.0은 HTTP 클라이언트 경계로 분리되어 있습니다.
</dependency>
```

모든 `ToolCallback` 빈을 URL 인자 검증으로 감쌈. **LLM 에이전트가 URL을 받아 fetch하는 시나리오의 결정적인 새 SSRF 표면.**
모든 `ToolCallback` 빈을 URL 인자 검증으로 감쌈. **LLM 에이전트가 URL을 받아 fetch하는 시나리오의 결정적인 새 SSRF 표면.** v3.1+는 내부적으로 `ssrf-guard-llm` (프레임워크-중립 코어)에 위임 — public API 동일.

=== "LangChain4j 툴 실행"

```xml
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-langchain4j</artifactId>
<version>3.1.0</version>
</dependency>
```

Spring AI 탭과 동일한 위협 모델, 다른 프레임워크. 모든 `ToolExecutor` 빈을 wrap. LangChain4j 툴이 Spring 빈일 때 `BeanPostProcessor`가 자동 처리; 프로그래매틱/비-Spring 사용에는 `SsrfGuardedToolExecutors.wrap(...)` 헬퍼가 `AiServices.builder(...).tools(Map<ToolSpecification, ToolExecutor>)` 형태 지원.

=== "커스텀 툴 dispatcher (프레임워크 없음)"

```xml
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-llm</artifactId>
<version>3.1.0</version>
</dependency>
```

프레임워크-중립 코어. 자체 툴 라우터 (MCP 서버, 내부 RPC dispatcher, 커스텀 에이전트 루프) 만든 경우 직접 사용:

```java
JsonToolInputGuard guard = new JsonToolInputGuard(urlPolicy);
String violation = guard.checkOrFormatError(rawJsonInput);
if (violation != null) return violation; // LLM에게 줄 구조화된 JSON
// ... 진짜 툴 실행
```

=== "JDK HttpClient (Spring 없음)"

Expand Down
33 changes: 32 additions & 1 deletion docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,38 @@ ssrf-guard v3.0.0 is split along HTTP-client boundaries — pick the module(s) m
</dependency>
```

Wraps every `ToolCallback` bean with URL-argument validation. The defining new SSRF surface — LLM agents that take a URL as a parameter and fetch it.
Wraps every `ToolCallback` bean with URL-argument validation. The defining new SSRF surface — LLM agents that take a URL as a parameter and fetch it. v3.1+ delegates to `ssrf-guard-llm` (the framework-agnostic core); same public API.

=== "LangChain4j tool execution"

```xml
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-langchain4j</artifactId>
<version>3.1.0</version>
</dependency>
```

Same threat model as the Spring AI tab — different framework. Wraps every `ToolExecutor` bean. Useful when your LangChain4j tools are Spring beans (auto-wrapped by `BeanPostProcessor`); for programmatic / non-Spring use, the `SsrfGuardedToolExecutors.wrap(...)` helpers cover the `AiServices.builder(...).tools(Map<ToolSpecification, ToolExecutor>)` shape.

=== "Custom tool dispatcher (no framework)"

```xml
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>ssrf-guard-llm</artifactId>
<version>3.1.0</version>
</dependency>
```

The framework-agnostic core. Use directly when you've built your own tool router (MCP server, internal RPC dispatcher, custom agent loop):

```java
JsonToolInputGuard guard = new JsonToolInputGuard(urlPolicy);
String violation = guard.checkOrFormatError(rawJsonInput);
if (violation != null) return violation; // structured JSON for the LLM
// ... run the real tool
```

=== "Plain JDK HttpClient (no Spring)"

Expand Down
21 changes: 21 additions & 0 deletions docs/guides/configuration.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,24 @@ ssrf:
```

이 정도 화이트리스트면 partner-integration이 많은 거의 모든 서비스를 SSRF에서 보호.

## LLM 어댑터 properties (v3.1+)

LLM 툴 빈 자동 wrap을 제어하는 두 토글. 둘 다 기본 `true` — 자체 코드에서 (`SsrfGuardedToolCallbacks.wrap(...)` / `SsrfGuardedToolExecutors.wrap(...)`) 선별적으로 wrap하고 싶으면 `false`로 끔.

| 키 | 기본값 | 효과 |
|---|---|---|
| `ssrf.guard.springai.wrap-tool-callbacks` | `true` | Spring AI 모든 `ToolCallback` 빈 자동 wrap. `ssrf-guard-springai` classpath 필요. |
| `ssrf.guard.langchain4j.wrap-tool-executors` | `true` | LangChain4j 모든 `ToolExecutor` 빈 자동 wrap. `ssrf-guard-langchain4j` classpath 필요. |

```yaml
ssrf:
guard:
enabled: true
exact-hosts:
- api.partner.com
springai:
wrap-tool-callbacks: true # 기본
langchain4j:
wrap-tool-executors: true # 기본
```
21 changes: 21 additions & 0 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,24 @@ ssrf:
```

A whitelist this size will keep almost any partner-integration-heavy service safe from SSRF.

## LLM-adapter properties (v3.1+)

Two additional toggles control automatic wrapping of LLM tool beans. Both default to `true` — set to `false` to wrap selectively from your own code (via `SsrfGuardedToolCallbacks.wrap(...)` or `SsrfGuardedToolExecutors.wrap(...)`) without the `BeanPostProcessor` running.

| Key | Default | Effect |
|---|---|---|
| `ssrf.guard.springai.wrap-tool-callbacks` | `true` | Wraps every Spring AI `ToolCallback` bean. Requires `ssrf-guard-springai` on the classpath. |
| `ssrf.guard.langchain4j.wrap-tool-executors` | `true` | Wraps every LangChain4j `ToolExecutor` bean. Requires `ssrf-guard-langchain4j` on the classpath. |

```yaml
ssrf:
guard:
enabled: true
exact-hosts:
- api.partner.com
springai:
wrap-tool-callbacks: true # default
langchain4j:
wrap-tool-executors: true # default
```
3 changes: 2 additions & 1 deletion docs/guides/security-model.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ Java 내장 `InetAddress.isSiteLocalAddress()`는 CGNAT, benchmark range, IPv6
솔직한 한계 목록. 경계 인식이 threat model의 일부.

- **HTTP 클라이언트마다 다른 URL 파싱을 검증하지 않음.** JDK `URI` 생성자, Spring `UriComponentsBuilder`, Apache HttpClient의 request line — `://user:pass@a.com\@b.com/` 같은 문자열이 어떤 호스트인지 항상 동의하지 않음. 신뢰할 수 없는 입력에서 URL을 받으면 `RestClient`에 넘기기 전 정규화해야. OWASP cheat sheet의 [URL parser confusion section](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html#network-layer) 권장.
- **`RestClient` 외 HTTP 클라이언트는 보호 안 함.** `HttpURLConnection`, `OkHttpClient`, 직접 Apache `HttpClient` (래핑 안 된), `WebClient` — 이 중 어떤 것도 SSRF 정책을 거치지 않음. 자동 구성은 Spring Boot 자동 `RestClient.Builder`만 customize.
- **wrap되지 않은 HTTP 클라이언트는 보호 안 함.** 자동 구성된 `RestClient.Builder` / `RestTemplateBuilder` / `WebClient.Builder` / `OkHttpClient.Builder` 등의 경로를 거치지 않고 직접 만든 `HttpURLConnection` 같은 코드는 SSRF 정책 우회. v3.1+는 주요 Java HTTP 스택 (RestClient · RestTemplate · WebClient · Feign · OkHttp · JDK `HttpClient` · Apache HttpClient 5)과 LLM 툴 dispatch (Spring AI · LangChain4j)를 모두 커버 — 사용 중인 각각에 맞는 모듈을 골라 추가.
- **WebClient는 v3.1부터 URL 단계 *+* DNS 단계 방어 모두.** v3.0.x WebClient 모듈은 URL 단계 필터만 실행 — 화이트리스트를 통과한 호스트가 사설 IP로 resolve되어도 reactor-netty가 그대로 연결. v3.1의 `SsrfGuardReactorAddressResolverGroup`이 reactor-netty의 address resolver에 후킹해서 다른 모듈과 동일한 사설/loopback 범위로 필터링. 비-Netty WebFlux 백엔드 (Jetty Reactive, Helidon)도 URL 단계 방어는 받음; connector 교체는 reactor-netty classpath 의존.
- **JVM 캐시가 작용할 때 DNS rebinding을 막지 않음.** Java가 DNS 해석을 캐시; JVM이 영구 캐싱 (Java 8u192 이전 보안 정책 default)이면 hostname의 record 변경 후에도 캐시된 IP를 계속 hit. 모던 JVM은 default가 30초이긴 함 — `networkaddress.cache.ttl` 합리적 값 유지.
- **`exact-hosts`에 사설 IP literal을 직접 넣는 걸 막지 않음.** `10.0.0.5`를 화이트리스트하면 인터셉터가 호스트를 통과시키고, DNS resolver가 그 IP로 단락. (단, `block-private-networks=false`로 안 두면) 사설 IP 필터가 여전히 적용되어 요청은 거부 — 하지만 레이어링이 "인터셉터 통과, resolver 거부"이지 "인터셉터 즉시 거부" 아님.
- **응답 본문 검증 안 함.** 화이트리스트 호스트라도 downstream 이슈를 트리거하는 콘텐츠 반환 가능. SSRF 방어는 신뢰하는 호스트로 소켓이 연결될 때 끝남; 그 호스트가 반환하는 건 애플리케이션 로직 책임.
Expand Down
3 changes: 2 additions & 1 deletion docs/guides/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ Java's built-in `InetAddress.isSiteLocalAddress()` misses CGNAT, the benchmark r
Honest list. Knowing the boundary is part of the threat model.

- **It does not validate URL parsing the way every HTTP client interprets it.** The JDK `URI` constructor, Spring's `UriComponentsBuilder`, Apache HttpClient's request line — they don't always agree on what host a `://user:pass@a.com\@b.com/` string represents. If you accept URLs from untrusted input, normalize them before handing them to `RestClient`. The OWASP cheat sheet has a [URL parser confusion section](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html#network-layer) worth reading.
- **It does not protect non-`RestClient` HTTP clients.** Code using `HttpURLConnection`, `OkHttpClient`, raw Apache `HttpClient` (not the wrapped one), `WebClient` — none of those go through the SSRF policy. The auto-configuration only customizes Spring Boot's auto-configured `RestClient.Builder`.
- **It does not protect non-wrapped HTTP clients.** Code using raw `HttpURLConnection`, or any client built outside the auto-configured `RestClient.Builder` / `RestTemplateBuilder` / `WebClient.Builder` / `OkHttpClient.Builder` etc. — that custom code bypasses the SSRF policy. v3.1+ covers every major Java HTTP stack (RestClient · RestTemplate · WebClient · Feign · OkHttp · JDK `HttpClient` · Apache HttpClient 5) plus LLM tool dispatch (Spring AI · LangChain4j) — pick the matching module for each one you use.
- **WebClient gets URL-time *and* DNS-time defense from v3.1.** v3.0.x's WebClient module only ran the URL-time filter — a host that passed the whitelist could still resolve to a private IP, and reactor-netty would connect to it. v3.1's `SsrfGuardReactorAddressResolverGroup` hooks into reactor-netty's address resolver and filters resolved IPs against the same private/loopback ranges. Non-Netty WebFlux backends (Jetty Reactive, Helidon) still get URL-time defense; the connector swap is gated on reactor-netty being on the classpath.
- **It does not protect against DNS rebinding when the JVM cache is in play.** Java caches DNS resolutions; if your JVM caches forever (the default for security policies that pre-date Java 8u192) and a hostname's records change after the cache was populated, the cached IP is what you keep hitting. Set `networkaddress.cache.ttl` to something sane (default in modern JVMs is already 30 seconds).
- **It does not stop you from putting a private-IP literal directly in `exact-hosts`.** If you whitelist `10.0.0.5`, the interceptor accepts that host, and the DNS resolver short-circuits to that IP. The private-IP filter still applies (unless you set `block-private-networks=false`), so the request is rejected — but the layering is "interceptor accepts, resolver rejects," not "interceptor rejects up front."
- **It does not validate response bodies.** A whitelisted host can still return content that triggers downstream issues. SSRF defense ends when the socket connects to a host you trust; what that host returns is application logic.
Expand Down
6 changes: 4 additions & 2 deletions docs/index.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ ssrf-guard는 작은 core(정책 + IP 분류) + HTTP 클라이언트별 모듈
| **`ssrf-guard`** | 메타 아티팩트 — RestClient + HttpClient5 (v2.0.0 호환) | Yes |
| `ssrf-guard-restclient` | Spring 6.1+ `RestClient` | Yes |
| `ssrf-guard-resttemplate` | Spring `RestTemplate` (엔터프라이즈/레거시) | Yes |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` | Yes (WebFlux) |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` — URL 단계 필터 **+** reactor-netty DNS 단계 IP 필터 (v3.1+) | Yes (WebFlux) |
| `ssrf-guard-feign` | Spring Cloud OpenFeign | Yes (Cloud) |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` — LLM 에이전트 SSRF 차단 | Yes (AI) |
| `ssrf-guard-llm` 🧩 | 프레임워크-중립 JSON 툴 입력 검증 (v3.1+). springai / langchain4j 어댑터가 사용; 커스텀 dispatcher에서도 직접 사용 가능. | No |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` — LLM 에이전트 SSRF 차단 (`-llm` 위의 thin adapter) | Yes (AI) |
| **`ssrf-guard-langchain4j`** ⭐ | LangChain4j `ToolExecutor` — Java LLM 양대 프레임워크 다른 한쪽 (v3.1+, `-llm` 위의 thin adapter) | Yes |
| `ssrf-guard-httpclient5` | Apache HttpClient 5 직접 사용 | No |
| `ssrf-guard-jdkhttp` | `java.net.http.HttpClient` (JDK 11+) | No |
| `ssrf-guard-okhttp` | OkHttp | No |
Expand Down
6 changes: 4 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ Pick the module(s) matching your HTTP client. The core is pulled in transitively
| **`ssrf-guard`** | Meta artifact — RestClient + HttpClient5 (v2.0.0 back-compat) | Yes |
| `ssrf-guard-restclient` | Spring 6.1+ `RestClient` | Yes |
| `ssrf-guard-resttemplate` | Spring `RestTemplate` (enterprise / legacy) | Yes |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` | Yes (WebFlux) |
| `ssrf-guard-webclient` | Spring WebFlux `WebClient` — URL-time filter **and** reactor-netty DNS-time IP filter (v3.1+) | Yes (WebFlux) |
| `ssrf-guard-feign` | Spring Cloud OpenFeign | Yes (Cloud) |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` — closes the LLM-agent SSRF surface | Yes (AI) |
| `ssrf-guard-llm` 🧩 | Framework-agnostic JSON tool-input validator (v3.1+). Used by the springai / langchain4j adapters; usable directly from a custom dispatcher. | No |
| **`ssrf-guard-springai`** ⭐ | Spring AI `ToolCallback` — closes the LLM-agent SSRF surface (thin adapter over `-llm`) | Yes (AI) |
| **`ssrf-guard-langchain4j`** ⭐ | LangChain4j `ToolExecutor` — same defense for the other Java LLM framework (v3.1+, thin adapter over `-llm`) | Yes |
| `ssrf-guard-httpclient5` | Apache HttpClient 5 directly | No |
| `ssrf-guard-jdkhttp` | `java.net.http.HttpClient` (JDK 11+) | No |
| `ssrf-guard-okhttp` | OkHttp | No |
Expand Down
Loading