Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/web-inbound-and-http-surface/case/case-a14-f002-webproblemsanitizer-alreadysafe.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

11 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 a14-f002-webproblemsanitizer-alreadysafe 호출자 없이 들어온 메서드이고, 도달 불가라던 가지는 켜지면 반대로 답한다 web-inbound-and-http-surface clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:a14-f002-webproblemsanitizer-alreadysafe 2026-09-02 case-a14-f002-webproblemsanitizer-alreadysafe.body.md
key file
a14-f002-webproblemsanitizer-alreadysafe ../../../final/evidence/rendered/a14-f002-webproblemsanitizer-alreadysafe.svg
key file
a14-f002-webproblemsanitizer-alreadysafe-branch ../../../final/evidence/rendered/a14-f002-webproblemsanitizer-alreadysafe-branch.svg
../../../final/evidence/raw/a14-f002-webproblemsanitizer-alreadysafe.txt
../../../final/evidence/raw/a14-f002-webproblemsanitizer-alreadysafe-branch.txt
원본 분석 절은 analysis/14-adapter-inbound-web.md#L380 이다.

호출자 없이 들어온 메서드이고, 도달 불가라던 가지는 켜지면 반대로 답한다

메시지가 이미 안전한지 단언하는 메서드다. 이름이 저장소에 딱 한 번, 자기 선언에만 나온다. 원문은 그 안의 삼항 한 가지도 도달 불가라고 적었는데, 실제로는 코드 점 스물셋이 그 가지를 타고 스물셋 다 안전하다는 답을 받는다.

관계

  • adapter까지 있고 호출자가 없는 교정 경로 만들어 두고 아무도 부르지 않는다는 점이 같다.
  • 소비자가 없는 fixture 셋 이쪽도 호출자 수를 세어 프로덕션에서 쓰이지 않는다는 것을 확인했다.
  • runtime_memberships를 먼저 읽고 심각도를 정한다 이 사례의 완화가 조립 부재가 아니라는 것을 그 규칙으로 판정했다.

문제

문제 응답 정제기에 안전 확인 메서드가 있고 자바독이 용도를 적는다. 그 용도가 수행되는지, 그리고 안쪽 조건이 정말 죽었는지 확인했다.

결론

호출자가 없다. 저장소가 추적하는 모든 파일에서 이 이름이 잡히는 자리가 선언 하나다.

쓰이다 끊긴 것도 아니다. 모든 참조를 뒤져도 이 이름을 건드린 커밋이 하나이고, 그 커밋이 이 파일을 새로 더한다.

안쪽 가지는 다르다. 원문은 앞선 검사가 공백만인 입력을 걸렀으니 다듬은 문자열이 빌 수 없고 따라서 삼항의 편집됨 가지는 도달하지 않는다고 적었다.

그 논리가 두 판정을 하나로 본다. 공백이냐를 묻는 쪽과 앞뒤를 떼어 내는 쪽이 서로 다른 집합을 쓰고, 그 차집합이 비어 있지 않다.

그 조각을 세어 봤다. 기본 다국어 평면 전체에서 스물셋이고, U+0000 부터 U+0008 까지와 U+000E 부터 U+001B 까지 두 구간이다. 널 문자와 백스페이스와 탈출 문자는 그중 셋일 뿐이다.

스물셋 모두 참을 받는다. 정제기와 삼항이 같은 편집 문구로 수렴하기 때문이다. 제어 문자 하나짜리 텍스트를 이미 안전하다고 단언하는 셈이다.

반대 방향도 나왔다. 삭제 문자는 다듬기가 떼지 않으므로 거짓이 되고, 붙임 공백 한 글자는 어느 규칙에도 걸리지 않아 참이 된다. 다만 그 글자를 상한보다 길게 늘이면 거짓으로 뒤집힌다. 정제기가 상한에서 잘라 낸 앞부분과 삼항이 고른 원문이 달라지기 때문이다.

그래서 원문이 적은 것과 다른 결함이 된다. 이 분기는 도달할 수 없는 코드가 아니라 실제로 실행되는 코드이고, 실행되면 원문이 예상한 것과 반대 값을 돌려준다.

판정은 P3 다. 호출자가 없으므로 오늘 그 답을 소비하는 코드가 없다.

다만 완화의 성격이 배선 부재는 아니다. 모듈 레지스트리에서 이 리프의 런타임 소속을 읽으면 둘이다. 남는 완화는 호출자가 없다는 것 하나뿐이고, 그것은 한 줄이면 사라진다. 그 상태를 붙들어 두는 장치도 없다. 이 리프가 선언한 구조 규칙은 여덟이고 두 목록에 담겨 실제로 도는 것은 일곱이다. 그것들이 보는 것은 컨트롤러와 트랜잭션과 와이어 모델과 엔벌로프와 의존 방향이고, 미호출 메서드를 보는 것은 없다. 목록에 담기지 않은 여덟 번째 자신도 호출자가 없다. 다만 그 여덟 번째 규칙의 자바독은 임포트가 놀지 않게 두는 표식이라고 스스로 밝힌다. 이 사례의 메서드는 하지 않는 일을 하겠다고 적어 두었다는 점이 다르다. 공개 표면 기준선도 이 리프에는 적용되지 않는다.

검증 환경

OpenJDK : 21.0.12 확인 방식 : 추적 파일 전수 검색과 모든 참조의 이력 확인, 기본 다국어 평면 전체 훑기, 구조 규칙 목록 확인 소스 수정 : x

재현 조건

이 사례의 프로브는 새로 만든 것이고 원문에는 없다. 원문 근거는 분석 문서의 #L380 절이다.

  1. 메서드와 그 자바독을 읽는다.
  2. 그 이름을 추적 파일 전부에서 대소문자 없이 찾는다.
  3. 모든 참조를 대상으로 그 이름을 건드린 커밋을 찾고, 그 커밋에서 파일 상태를 본다.
  4. 앞선 검사와 다듬기가 각각 어떤 판정을 쓰는지 읽는다.
  5. 두 판정이 갈리는 코드 점을 기본 다국어 평면 전체에서 센다.
  6. 그중 메서드가 참을 돌려주는 것이 몇 개인지 함께 센다.
  7. 모듈 레지스트리에서 이 리프의 런타임 소속을 읽고, 이 리프의 구조 규칙이 무엇을 보는지 나열한다.

본문

자바독이 용도를 한 줄로 적는다.

// WebProblemSanitizer.java:78-84
  /** Whether text would survive sanitising unchanged, for asserting a message is already safe. */
  public boolean alreadySafe(String input, int maxLength) {
    return input != null
        && !input.isBlank()
        && sanitize(input, maxLength)
            .equals(input.trim().toLowerCase(Locale.ROOT).isEmpty() ? REDACTED : input.trim());
  }

부르는 곳이 없고, 있었던 적도 없다

:::evidence key="a14-f002-webproblemsanitizer-alreadysafe" alt="코드베이스 정적 검색 출력 51줄. 메서드와 자바독, 추적 파일 전부에서 그 이름을 찾은 결과 한 줄, 모든 참조에서 그 이름을 건드린 커밋과 그 커밋에서의 파일 상태, 앞선 검사와 다듬기가 쓰는 판정 줄, 이 리프의 런타임 소속, 선언된 구조 규칙 여덟과 그중 두 목록에 담겨 실제로 도는 일곱, 공개 표면 기준선 적용 여부가 차례로 보인다." caption="이름이 나오는 자리와 그 이력, 리프의 소속과 구조 규칙 — 51줄 · exit 0" zoom="true" :::

git grep -i 는 확장자를 가리지 않는다. 그런데도 잡히는 줄이 WebProblemSanitizer:79 하나다. git log --all -S 도 커밋을 하나만 내고, 그 커밋에서 이 파일의 상태가 A 다. 호출자를 잃은 것이 아니라 없이 들어왔다.

도달 불가라는 근거가 성립하지 않는다

원문은 !input.isBlank() 가 통과했으니 input.trim() 이 빌 수 없고, 따라서 삼항의 REDACTED 가지는 도달 불가라고 적는다. 그 추론에는 두 판정이 같은 것으로 놓여 있다.

String.isBlank()Character.isWhitespace 를 쓴다. String.trim() 이 떼어 내는 것은 문자열 양끝의 U+0020 이하다. 한쪽이 공백으로 세지 않는 문자를 다른 쪽이 지우면 앞선 검사를 통과한 입력의 다듬은 값이 빌 수 있다.

갈리는 코드 점을 전부 셌다

:::evidence key="a14-f002-webproblemsanitizer-alreadysafe-branch" alt="JVM 프로브 출력 18줄. 열 가지 입력마다 isBlank 결과와 다듬은 문자열이 비는지와 삼항이 타는 가지와 alreadySafe 의 반환을 한 줄에 적고, 이어서 기본 다국어 평면 전체에서 두 판정이 갈리는 코드 점의 수와 구간과 그중 참을 받는 수를 적었다." caption="열 입력의 가지와 갈리는 코드 점 전수 — 18줄 · exit 0" zoom="true" :::

앞 표가 예시 열 개다. 널 문자 하나, 널 문자 셋, 백스페이스, 탈출 문자가 그 가지를 타고 넷 다 참을 받는다. sanitize 도 그 입력에서는 REDACTED 를 돌려주므로 등호가 성립한다.

예시로는 범위를 말할 수 없어 뒤에서 기본 다국어 평면 전체를 훑었다. 갈리는 코드 점이 스물셋이고 U+0000..U+0008U+000E..U+001B 두 구간이다. U+0020 이하는 서른셋인데 그중 열이 빠진다. U+0009..U+000DU+001C..U+0020 이 자바에서는 공백이라 앞선 검사에 걸리기 때문이다. 가운데가 끊기는 이유도, 두 번째 구간이 U+001F 가 아니라 U+001B 에서 멈추는 이유도 그것이다. 정보 구분자 넷이 공백으로 세어진다는 것이 위쪽 경계를 정한다.

스물셋 가운데 참을 받는 것이 스물셋이다. 예시 넷은 대표값이고 방향이 갈리는 코드 점은 없다.

경계 두 개가 더 있다

U+007F 는 U+0020 보다 크므로 다듬기가 떼지 않는다. 삼항은 원문 가지를 고르고 sanitizeCONTROL 규칙으로 그것을 지우므로 두 값이 갈려 거짓이 된다.

U+00A0Character.isWhitespace 가 거짓이고 다듬기도 떼지 않는다. REMOVALS 의 여섯과 CONTROL, WHITESPACE 어느 것에도 걸리지 않아 입력이 그대로 살아남고 참이 된다. 눈에 보이지 않는 한 글자가 이미 안전한 메시지로 통과한다.

길이는 별개다. 같은 글자를 maxLength 보다 길게 늘이면 :70-74 의 잘림이 개입해 거짓이 된다.

정제기에서 값을 바꾸는 것은 여덟 규칙만이 아니다. 규칙을 적용한 뒤 :66 이 한 번 더 다듬고, 빈 값은 :58-60·:67-69·:75 에서 편집 문구로 바뀌며, 상한을 넘으면 :70-74 가 잘라 낸다. 제어 문자 스물셋에는 앞의 둘이 이어서 걸린다. CONTROL 이 그 글자를 공백 하나로 바꾸고, :66 이 그것을 다듬어 값을 비우고, 그제서야 :67-69 가 편집 문구를 돌려준다. :66 을 빼면 값이 공백 하나로 남아 스물셋이 전부 거짓이 된다. 앞뒤 공백이 참을 받는 것은 첫째 장치 하나 때문이고, 길이가 걸리는 것은 마지막 장치다.

확인하지 못한 것

보충 평면은 훑지 않았다. 자바의 문자 하나가 담지 못하는 값이라 이 판정에 걸릴 수 없다고 보았을 뿐 세어 보지는 않았다. 정제기의 다른 메서드에도 같은 판정 불일치가 있는지도 세지 않았다.