Skip to content

fix(llm): let the policy judge non-http schemes and protocol-relative URLs - #20

Merged
jlc488 merged 1 commit into
mainfrom
fix/scanner-scheme-parity
Aug 8, 2026
Merged

fix(llm): let the policy judge non-http schemes and protocol-relative URLs#20
jlc488 merged 1 commit into
mainfrom
fix/scanner-scheme-parity

Conversation

@jlc488

@jlc488 jlc488 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

JVM ↔ JS 정합성 감사에서 나온 건입니다. 그리고 3.1.1에서 고친 대문자 스킴 우회와 같은 형태입니다 — 수집 단계의 필터가 URL을 정책 검증에서 통째로 빠져나가게 하는 것.

무엇이 잘못돼 있었나

JsonToolInputGuardhttp://·https://로 시작하는 문자열만 수집하고, 그 뒤에 http(s)가 아닌 스킴은 정책 호출 전에 버렸습니다.

if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) continue;

그래서 LLM 툴 입력의 file:///etc/passwd, gopher://…, ftp://…조용히 통과했습니다. allowedSchemes에 의해 거부된 게 아니라, 아예 보이지 않았습니다.

프로토콜 상대 참조(//evil.example/x)는 수집조차 안 됐습니다. 페치 시점에 호출자의 스킴을 물려받으므로 실제 페치 대상인데도요.

고친 방식

수집은 관대하게, 판단은 정책이. JS 자매가 0.2.0부터 하던 것과 동일합니다:

  • 모든 scheme:// 수집 → allowedSchemes가 거부 → file://이 이제 blocked_scheme
  • //authority 수집 → 호스트 목적상 https로 간주해 검증 → 호스트 허용 목록이 판단
  • authority 없는 스킴(mailto:·urn:·data:)은 계속 무시 — 검사할 호스트가 없고 페치 표면도 아님
  • scanEmbedded 패턴에도 같은 두 규칙 적용

옛 동작을 못 박고 있던 테스트

void ignores_non_http_schemes() {
    // mailto:, urn:uuid:, file:// — should not trip the URL detector

버그가 기대값으로 적혀 있었습니다. mailto:/urn:(authority 없음 → 무시)과 file:///gopher:///ftp://(→ blocked_scheme)를 각각 구분해서 못 박는 테스트로 교체했고, 프로토콜 상대는 허용/거부/주석 세 경우를 넣었습니다.

영향

ssrf-guard-springai, ssrf-guard-langchain4j. 미출시 3.2.0에 들어갑니다 (3.2.0-SNAPSHOT이라 릴리스된 사용자는 없습니다).

검증

./gradlew build → BUILD SUCCESSFUL
전 모듈 231 tests, failures=0 errors=0

CHANGELOG 3표면(루트 · docs/changelog.md · docs/changelog.ko.md) 모두 갱신했습니다.

감사에서 나온 나머지

JS 쪽 갭 2건(::, fec0::/10 미분류)은 ssrf-guard-js에서 별도로 고칩니다. 리다이렉트 홉 재검증 범위 차이(자바는 스킴+DNS만, JS는 포트·userinfo·IP-리터럴까지)는 어댑터 6종에 걸친 별건이라 이번 PR 범위 밖으로 두고 정합성 문서에 미해결로 기록합니다.

… URLs

Found by the first JVM <-> JS parity audit, and it is the same shape as
the uppercase-scheme bypass fixed in 3.1.1: a filter at the COLLECTION
stage letting a URL skip policy validation entirely.

JsonToolInputGuard collected only strings starting `http://` or
`https://`, and then discarded anything whose scheme was not http(s)
before calling the policy. So `file:///etc/passwd`, `gopher://…`,
`ftp://…` in LLM tool input passed the guard in silence — they were never
rejected by allowedSchemes, they were never seen. Protocol-relative
references (`//evil.example/x`) were not collected at all, though they
inherit the caller's scheme at fetch time and are therefore real fetch
targets.

Collection is now generous and the policy decides, matching what the JS
sibling has done since @devslab/ssrf-guard-js 0.2.0:

  - any `scheme://` is collected; allowedSchemes rejects what it should,
    so `file://` now yields blocked_scheme instead of nothing;
  - `//authority` is collected and validated as https for host purposes,
    so it is judged by the host allowlist;
  - schemes with no authority (mailto:, urn:, data:) stay ignored — no
    host to check, not a fetch surface;
  - the embedded (scanEmbedded) patterns get the same two rules.

One existing test asserted the old behaviour — "file:// should not trip
the URL detector" — which was the bug written down as an expectation. It
is replaced by tests that pin what mailto:/urn: (ignored, no authority)
and file://gopher://ftp:// (blocked_scheme) each do, plus protocol-
relative accept/reject/comment cases.

Affects ssrf-guard-springai and ssrf-guard-langchain4j.

Verified: ./gradlew build BUILD SUCCESSFUL, 231 tests across all modules,
failures=0 errors=0. Changelog updated in all three surfaces (root,
docs/changelog.md, docs/changelog.ko.md) under the unreleased 3.2.0.
@jlc488
jlc488 merged commit 39bb441 into main Aug 8, 2026
1 check passed
@jlc488
jlc488 deleted the fix/scanner-scheme-parity branch August 8, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant