MCP Java SDK 도입으로 들어온 json-schema-validator는 schema의 $ref가 문서
밖을 가리키면 그 주소로 직접 조회하고, pattern 검증을 백트래킹 기반
java.util.regex로 처리한다. inputSchema는 Tool Service 매니페스트에서 오므로
매니페스트가 서버의 outbound 대상과 CPU 소비를 정할 수 있었다. AGENTS.md의
"outbound 주소는 설정에서만 온다"는 불변식이 이 경로에서 뚫려 있었다.
DefaultJsonSchemaValidator는 SchemaRegistry를 생성자 안에서 만들고 private
final로 들고 있어 정책 주입 지점이 없다. 따라서 SDK 밖에서만 막을 수 있다.
검사는 ToolMetadata의 표준 생성자에 둔다. Portal 매니페스트, local 파일,
Redis snapshot 역직렬화가 모두 이 생성자를 지나므로 우회 경로가 생기지 않는다.
위반은 기존 매니페스트 형식 오류와 같게 다뤄 bundle 단위 실패 격리와
"Redis 실패는 언제나 cache miss" 동작을 그대로 유지한다.
정규식 규칙은 JDK 21.0.11 실측으로 정했다. 통념과 달리 (a+)+는 빠르게 끝나고,
중첩이 아닌 a*a*a*a*a*b와 바깥 반복이 유한한 (.*,){11}P가 폭증했다. 겹치는
문자 집합 판정은 결정 불가능하므로 모양 검사만으로는 부족하고, pattern 필드에
maxLength 동반 선언을 요구해 입력 길이를 묶는 것이 실질적인 상한이 된다.
patternProperties는 key에 길이를 선언할 자리가 없어 사용을 금지한다.
format은 단언되지 않아 format:regex 경로가 실행되지 않는다는 사실도 계약
테스트로 고정했다. SDK 업그레이드로 단언이 켜지면 테스트가 실패한다.
Tool Service는 pattern을 쓰는 필드에 maxLength(<=256)를 선언해야 하므로
매니페스트 수용 조건이 바뀐다. Tool Service 파트와 합의가 필요하다.
근거: ADR-0011, ADR-0012
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.2 KiB
ADR-0011 Tool inputSchema는 문서 밖을 참조하지 않는다
배경
MCP Java SDK를 도입하면서 JSON Schema 2020-12 검증을 com.networknt:json-schema-validator에 위임했다
(mcp-java-sdk-adoption.md). 그런데 JSON Schema의 $ref는 같은 문서 안뿐 아니라
다른 주소의 문서를 가리킬 수 있고, 검증기는 그런 참조를 만나면 그 주소로 직접 조회를 시도한다.
inputSchema는 Tool Service 매니페스트에서 온다. 즉 매니페스트에 이런 schema가 실리면
{"type":"object","properties":{"q":{"$ref":"http://any-host/whatever.json"}}}
MCP가 그 주소로 요청을 보낸다. 이것은 AGENTS.md §2의 불변식과 정면으로 어긋난다.
outbound 주소는 설정에서만 온다. 요청 값도 매니페스트도 호출 대상을 바꾸지 못한다.
매니페스트가 선언한 endpoint를 무시하는 규칙은 이미 있고 테스트로 잠겨 있다. $ref는 같은 불변식을
같은 방식으로 깨는데 통제가 없던 경로였다. SDK 도입이 열어 놓은 구멍이다.
ADR-0006에 따라 MCP는 인증·인가를 하지 않으므로, 이 경로 앞에서 호출자를 걸러 주는 계층도 없다.
SDK 설정으로는 막을 수 없다
DefaultJsonSchemaValidator는 SchemaRegistry를 생성자 안에서 직접 만들고 private final로 들고 있다.
공개 생성자는 ()와 (ObjectMapper) 둘뿐이라, 참조 해석 정책을 담은 설정을 밖에서 넣을 자리가 없다.
검증기 쪽에서 끄는 선택지는 존재하지 않는다.
결정
Tool의 inputSchema는 문서 밖을 가리키는 참조를 담을 수 없다. 검증기에 넘기기 전에, schema가 Registry로
들어오는 시점에 거부한다.
| 대상 | 규칙 |
|---|---|
$ref, $dynamicRef |
값이 #으로 시작해야 한다. 즉 같은 문서 안의 위치만 가리킨다 |
$schema |
선언했다면 https://json-schema.org/draft/2020-12/schema여야 한다 |
$id |
제한하지 않는다 |
$id를 열어 두는 이유는, 문서 밖 참조가 모두 막히면 base URI가 무엇이든 조회가 일어나지 않기 때문이다.
막을 이유가 없는 것까지 막으면 정상 Tool만 거부된다.
검사 지점은 ToolMetadata의 표준 생성자다. Portal 매니페스트 파싱, local 파일 로딩, Redis snapshot 역직렬화가
모두 이 생성자를 지나므로 경로마다 검사를 흩어 놓지 않아도 우회 경로가 생기지 않는다.
위반은 기존 매니페스트 형식 오류와 같게 다룬다. 따라서 bundle 단위 실패 격리와 "Redis 실패는 언제나 cache miss" 불변식이 그대로 적용되고, 한 Tool의 잘못된 schema가 다른 bundle의 정상 Tool을 지우지 않는다.
검토한 대안
| 대안 | 채택하지 않은 이유 |
|---|---|
| 검증기 설정으로 원격 해석 차단 | 위 절대로 주입 지점이 없다 |
JsonSchemaValidator를 직접 구현 |
SDK에 표준 검증을 위임한다는 도입 전제를 되돌리게 된다. networknt API를 우리가 떠안고, SDK 업그레이드마다 정책이 조용히 어긋날 수 있다 |
| egress 방화벽만으로 차단 | 심층 방어로는 유효하지만 단독으로는 부족하다. 플랫폼 설정에 의존하고, 차단되지 않은 내부 주소에는 여전히 도달한다 |
egress 통제는 이 결정을 대체하지 않고 함께 둔다.
영향
- Tool Service는
inputSchema를 자기 문서 안에서 완결시켜야 한다. 공통 타입은$defs로 같은 문서에 넣고#/$defs/...로 참조한다. 이 항목은 합의 대상이 아니라 계약이므로 extension-points.md의 협의 목록에서 뺀다. format키워드의 검증 강도와 허용 keyword 범위는 여전히 미확정이며 협의 목록에 남는다.- 새 참조 keyword가 JSON Schema에 추가되면 이 결정을 함께 갱신한다. 규칙은
ToolSchemaReferencePolicy가 소유하고ToolSchemaReferencePolicyTest가 잠근다.