The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.0 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | analysis-finding-a16-f005 | 설정으로 정한 파서 한계가 graphql-java에 설치되지 않는다 | graphql-surface | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:analysis-finding-a16-f005 | 2026-09-01 | case-analysis-finding-a16-f005.body.md |
|
|
|
설정으로 정한 파서 한계가 graphql-java에 설치되지 않는다
파서 계층은 이 방어의 첫 번째 관문이다. 리프가 한계를 값으로 갖고 설치 함수도 갖는데 호출하는 코드가 없다. 파서 옵션이 정적 전역이라 시작 시 한 번 설치하지 않으면 라이브러리 기본값이 유지된다.
관계
- 배열 원소 상한이 선언만 되고 강제되지 않으며 백스톱도 없다 같은 형태의 선언과 강제 어긋남이다.
- 5계층 예산 모델에서 요청 계층만 강제되고 나머지 파생이 전부 미배선이다 같은 리프의 다른 미배선 사례다.
- 설정은 받아들여지고 검증되며 효과가 없다 이 사례가 그 규칙의 형태다.
문제
파서 계층은 이 프로토콜의 서비스 거부 방어에서 첫 번째 관문이다.
복잡도 계산도 구조 분석도 문서를 파싱한 뒤에 일어나므로, 파싱 자체를 폭발시키는 문서는 그 앞에서 막아야 한다.
이 리프가 그 한계를 실제로 거는지 확인했다.
결론
걸지 않는다.
한계를 값으로 갖는 타입이 있고 클라이언트 정책에서 파생된다.
설치 함수도 있다. 연산 기본값 설치라는 이름이다.
호출하는 코드가 없다.
이름이 가리키듯 이 라이브러리의 파서 옵션은 정적 전역이다. 시작 시 한 번 설치하지 않으면 라이브러리 기본값이 유지된다.
실패 시나리오는 이렇다.
운영자가 설정으로 파서 한계를 조인다.
그 값은 플랫폼 설정을 거쳐 클라이언트 정책까지 도달한다. 그러나 한계 타입을 만드는 팩토리를 부르는 코드가 없어 파서에 닿지 않는다.
실제로 적용되는 것은 라이브러리의 기본값이다.
설정은 받아들여지고 검증되며 효과가 없다.
노출의 크기는 라이브러리 기본값이 정한다.
이 판본은 토큰 수와 공백 토큰 수와 규칙 깊이에 자체 기본 상한을 두므로 무제한은 아니다.
그리고 구조 한계와 복잡도 계산은 배선되어 있어 파싱 이후 계층은 작동한다.
그래서 P1 이 아니라 P2 다. 침묵하는 설정 표면이자 방어 계층 하나의 부재다.
권고는 플랫폼 자동 설정에 시작 시 한 번 설치를 부르는 초기화 지점을 두는 것이다.
정적 전역이므로 빈 메서드보다 초기화 콜백이 적절하다.
검증 환경
확인 방식 : 설치 함수 호출자 검색과 파서 옵션 전역성 확인 소스 수정 : x
재현 조건
원문은 final/evidence/raw/182 계열에 있다.
- 파서 한계 타입과 설치 함수를 확인한다.
- 설치 함수의 호출자를 센다.
- 라이브러리의 파서 옵션이 정적 전역인지 확인한다.
- 설정 값이 어디까지 도달하는지 추적한다.
- 구조 한계와 복잡도 계산이 배선되는지 확인한다.
본문
파서 계층은 GraphQL DoS 방어의 첫 번째 관문이다 — 복잡도 계산도 구조 분석도 문서를 파싱한 뒤에 일어나므로, 파싱 자체를 폭발시키는 문서는 그 앞에서 막아야 한다.
파서 계층이 첫 관문인 이유
:::evidence key="analysis-finding-a16-f005" alt="분석 문서 analysis/16-adapter-inbound-graphql.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/16-adapter-inbound-graphql.md 발췌 — 15줄" zoom="true" :::
값도 있고 설치 함수도 있고 호출하는 코드가 없다
이 leaf는 그 한계를 값으로 갖고(GraphQlParserLimits, 클라이언트 정책에서 파생), 설치 함수를 갖는다(GraphQlParserOptionsFactory.installOperationDefaults). installOperationDefaults라는 이름이 가리키듯 graphql-java의 파서 옵션은 정적 전역(ParserOptions.setDefaultOperationParserOptions)이고, 시작 시 한 번 설치하지 않으면 라이브러리 기본값이 유지된다.
실패 시나리오
운영자가 backend.graphql.limits.*로 파서 한계를 조인다. 그 값은 GraphQlPlatformSettings → GraphQlClientPolicy까지 도달하지만 GraphQlParserLimits.from(...)을 부르는 코드가 없어 파서에 닿지 않는다. 실제로 적용되는 것은 graphql-java 25.0의 기본값이다. 설정은 받아들여지고 검증되며 효과가 없다.
노출의 크기는 라이브러리 기본값이 정한다
graphql-java 25.0은 토큰 수·공백 토큰 수·규칙 깊이에 자체 기본 상한을 두므로 무제한은 아니다. 그리고 구조 한계(GraphQlDocumentShapeAnalyzer, autoconf=4)와 복잡도 계산(autoconf=4)은 배선돼 있어 파싱 이후 계층은 작동한다. 그래서 P1이 아니라 P2다 — 침묵하는 설정 표면이자 방어 계층 하나의 부재다.
권고
GraphQlPlatformAutoConfiguration에 시작 시 GraphQlParserOptionsFactory.installOperationDefaults(GraphQlParserLimits.from(clientPolicy))를 한 번 호출하는 초기화 지점을 둔다. 정적 전역이므로 @Bean 메서드보다 InitializingBean/SmartInitializingSingleton이 적절하다.
확인하지 못한 것
파서를 폭발시키는 문서를 보내 라이브러리 기본값이 적용되는지 재현하지 않았다. 호출자 부재상 그 결과가 나온다.