Files
dap-was-dapms/docs/decisions/ADR-0011-tool-input-schema-stays-in-document.md
koseokmin ad7fccbed1 Tool inputSchema에 참조·정규식 정책을 적용한다
매니페스트가 선언한 inputSchema는 외부가 정하는 입력이다. JSON Schema
검증기는 문서 밖 $ref를 만나면 그 주소로 직접 조회하므로 매니페스트가
서버의 outbound 호출 대상을 정하는 통로가 된다. pattern은 joni와
graal-js가 없어 java.util.regex의 백트래킹 경로로 처리되고, 인증이 없는
경계(ADR-0006)라 호출 빈도를 줄여 주는 계층도 없다.

ToolSchemaReferencePolicy가 문서 밖 참조와 미지원 dialect를 막고,
ToolSchemaPatternPolicy가 정규식 길이·무한 수량자 개수·중첩 반복을
검사하며 pattern을 쓰는 필드에 maxLength를 요구한다. 길이를 묶을 수
없는 patternProperties는 거부한다.

검사는 ToolMetadata의 표준 생성자 한 곳에서만 한다. Portal 매니페스트
파싱, local 파일 로딩, Redis snapshot 역직렬화가 모두 이 생성자를
지나므로 조회 경로가 늘어도 검사 지점은 하나로 남는다. 위반은
IllegalStateException이라 기존 매니페스트 형식 오류와 같게 다뤄지고
bundle 단위 실패 격리가 그대로 적용된다.

근거와 한계는 ADR-0011, ADR-0012에 있다. ADR-0012가 classpath 근거로
인용하는 docs/sbom도 함께 가져온다.

192개 테스트 전부 통과. 기존 169건은 새 검사에 걸리지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 17:20:50 +09:00

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 설정으로는 막을 수 없다

DefaultJsonSchemaValidatorSchemaRegistry를 생성자 안에서 직접 만들고 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가 잠근다.