fix(llm): let the policy judge non-http schemes and protocol-relative URLs - #20
Merged
Conversation
… 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.
This was referenced Aug 8, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
첫 JVM ↔ JS 정합성 감사에서 나온 건입니다. 그리고 3.1.1에서 고친 대문자 스킴 우회와 같은 형태입니다 — 수집 단계의 필터가 URL을 정책 검증에서 통째로 빠져나가게 하는 것.
무엇이 잘못돼 있었나
JsonToolInputGuard가http://·https://로 시작하는 문자열만 수집하고, 그 뒤에 http(s)가 아닌 스킴은 정책 호출 전에 버렸습니다.그래서 LLM 툴 입력의
file:///etc/passwd,gopher://…,ftp://…가 조용히 통과했습니다.allowedSchemes에 의해 거부된 게 아니라, 아예 보이지 않았습니다.프로토콜 상대 참조(
//evil.example/x)는 수집조차 안 됐습니다. 페치 시점에 호출자의 스킴을 물려받으므로 실제 페치 대상인데도요.고친 방식
수집은 관대하게, 판단은 정책이. JS 자매가
0.2.0부터 하던 것과 동일합니다:scheme://수집 →allowedSchemes가 거부 →file://이 이제blocked_scheme//authority수집 → 호스트 목적상 https로 간주해 검증 → 호스트 허용 목록이 판단mailto:·urn:·data:)은 계속 무시 — 검사할 호스트가 없고 페치 표면도 아님scanEmbedded패턴에도 같은 두 규칙 적용옛 동작을 못 박고 있던 테스트
버그가 기대값으로 적혀 있었습니다.
mailto:/urn:(authority 없음 → 무시)과file:///gopher:///ftp://(→blocked_scheme)를 각각 구분해서 못 박는 테스트로 교체했고, 프로토콜 상대는 허용/거부/주석 세 경우를 넣었습니다.영향
ssrf-guard-springai,ssrf-guard-langchain4j. 미출시 3.2.0에 들어갑니다 (3.2.0-SNAPSHOT이라 릴리스된 사용자는 없습니다).검증
CHANGELOG 3표면(루트 ·
docs/changelog.md·docs/changelog.ko.md) 모두 갱신했습니다.감사에서 나온 나머지
JS 쪽 갭 2건(
::,fec0::/10미분류)은 ssrf-guard-js에서 별도로 고칩니다. 리다이렉트 홉 재검증 범위 차이(자바는 스킴+DNS만, JS는 포트·userinfo·IP-리터럴까지)는 어댑터 6종에 걸친 별건이라 이번 PR 범위 밖으로 두고 정합성 문서에 미해결로 기록합니다.