Files
llm-wiki/docs/superpowers/specs/2026-06-04-ca-tmpl-optimization-per-file-findings.md

85 KiB

title, source_type, status, confidence, tags, related_projects, last_reviewed
title source_type status confidence tags related_projects last_reviewed
ca-tmpl (Clean Architecture Template) Audit Detailed Per-File Findings llm-generated verified high
architecture
clean-architecture
security
audit
ca-tmpl
2026-06-04

ca-tmpl (Clean Architecture Template) Audit Detailed Per-File Findings

본 문서는 /raw/branch-notes 하위의 81개 전체 명세 파일에 대한 개별 감사 발견 사항의 전문을 수록한 상세 리포트입니다.


4.1 ### (Status: READ_FULL)

  • Source file: raw/branch-notes/feature-api-compatibility-deprecation-contract.md
  • Source quote: ⚠️ OpenAPI Operation Object 의 deprecated: boolean 필드 자체는 OPENAPI31-C1~C7 발췌에 포함되지 않음 (raw 자체 Usage Boundary 명시)
  • Source line: 100
  • Severity: Medium
  • Claim: OpenAPI Spec 3.1.0에서 deprecated 필드가 schema element의 deprecation을 규범적으로 보장하며 metadata 계약으로 활용된다.
  • Assumptions: openapi-spec-3-1-0 raw 파일이 OpenAPI 3.1.0 specification의 deprecated behavior를 충분히 corroborating하고 있다.
  • Failure mode: OpenAPI Spec 3.1.0 reference raw에 deprecated element가 누락되어 design decision (D8)에 근거가 부족해지고, generator/validator 도구(springdoc 등)가 specification compliant한 방식으로 deprecation을 client에 알리는 계약을 검증하기 어려워짐.
  • Falsification condition: OpenAPI 3.1.0 spec raw 문서에 Operation/Schema Object의 deprecated 필드 syntax/semantics 발췌가 수록되지 않고도 contract test가 formal specification 정합성을 완전 검증할 수 있는 경우.
  • Recommendation: raw/official-docs/openapi-spec-3-1-0.md 혹은 별도의 raw reference 파일에 OpenAPI 3.1.0 Specification Section 4.8.10 (Operation Object) 및 Section 4.8.24 (Schema Object)의 deprecated keyword normative definition을 추가하여 D8의 evidence strength를 official-standard로 보정할 것.
  • Verification command: grep -rn "deprecated" raw/official-docs/openapi-spec-3-1-0.md
  • Verification result: OpenAPI Specification 3.1.0 raw 발췌에 deprecated 필드 자체의 syntax/semantics mapping description이 없음이 확인됨.

L2-F02: 414 URI Too Long end-to-end integration test 및 Tomcat validation gate의 미완성

  • Source file: raw/branch-notes/feature-api-contract-baseline.md
  • Source quote: planned 잔존: D22 HMAC 운영 key/회전(security), D8 414 end-to-end(Tomcat pre-dispatch)
  • Source line: 170 / 185
  • Severity: Medium
  • Claim: HTTP URI/Query parameter 길이 제한 초과 시 web server (Tomcat) pre-dispatch level에서 414 URI Too Long 응답이 envelope 형식에 정합하게 반환된다.
  • Assumptions: Tomcat의 기본 maxHttpHeaderSize (8KB)에 의해 dispatcher servlet 도달 전 거부되는 경우에도 Spring GlobalExceptionHandler가 에러를 envelope로 자동 wrap할 수 있다.
  • Failure mode: Tomcat이 Spring dispatcher servlet으로 dispatch하기 전 connection 레벨에서 raw HTTP 400/414 응답을 반환하여 ca-tmpl의 success/error envelope symmetry 계약을 우회하고 leak을 유발함.
  • Falsification condition: Tomcat pre-dispatch 거부 에러 핸들러나 gateway-level wrap 설정 없이도 414 URI Too Long이 default Tomcat response로 나가는 것이 safe behavior로 인정되는 경우.
  • Recommendation: Embedded Tomcat customizer를 작성하여 maxHttpHeaderSize 초과 시 web server가 raw response를 반환하는 대신, error dispatch page를 /error로 포워딩하여 GlobalExceptionHandler가 wrap하도록 하거나, gateway-pre-reject layer의 mapping contract를 formalize할 것.
  • Verification command: grep -n "D8 414 end-to-end" raw/branch-notes/feature-api-contract-baseline.md
  • Verification result: Line 170: D8 414 end-to-end(Tomcat pre-dispatch — code+registry row만) 및 Line 185: planned 잔존: ... D8 414 end-to-end(Tomcat pre-dispatch)로 integration test가 planned 상태에 머물러 있음이 확인됨.

L3-F03: inNew (REQUIRES_NEW)의 concurrent execution 하 connection pool exhaustion & deadlock 위험성 제어 미비

  • Source file: raw/branch-notes/feature-application-port-usecase-contract.md
  • Source quote: inNew 호출은 새 physical JDBC connection 획득 (outer transaction 의 connection 은 그대로 점유). Pool sizing 제약 ... Forbidden: inNew 를 loop 안에서 per-record 호출
  • Source line: 81
  • Severity: High
  • Claim: HikariCP connection pool이 hikari.maximumPoolSize >= (concurrent_threads * (1 + max_inNew_depth)) + 1 공식을 준수하여 dynamic lock 상황에서 pool starvation deadlock을 방지할 수 있다.
  • Assumptions: 개발자가 loop 안에서 inNew를 호출하지 않는다는 ArchUnit rule이나 정적 강제 수단이 부재하더라도 code review만으로 deadlock 발생을 사전에 완전 방지할 수 있다.
  • Failure mode: 개발자가 custom loop나 recursive call 내에서 inNew (requires_new)를 간접 호출하여 connection pool을 고갈시키고, active thread들이 physical connection을 대기하며 deadlock 상태에 빠짐.
  • Falsification condition: Loop 내 inNew 호출을 compile-time 또는 static analysis 단계에서 static rule로 검출할 방법이 전혀 없어서 manual check에만 전적으로 의존해야 하는 경우.
  • Recommendation: inNew loop 내 호출을 탐지하는 custom ArchUnit rule (inNew_is_not_called_inside_loops) 또는 TransactionPort.inNew 내에 thread-local depth counter를 두어 runtime limit (예: depth > 1 시 경고/예외)을 강제하는 sentinel 메커니즘을 추가할 것.
  • Verification command: grep -n "inNew" raw/branch-notes/feature-application-port-usecase-contract.md
  • Verification result: Line 81 및 Line 164에 inNew pool sizing 공식과 loop 호출 금지 정책은 있으나, 이를 검증하는 ArchUnit rule이나 runtime dynamic restriction이 설계 및 구현에서 누락됨.

L3-F04: Application Query Bypass의 RESEARCH_PENDING 상태 방치로 인한 읽기 최적화 가이드라인 공백

  • Source file: raw/branch-notes/feature-application-query-bypass-contract.md
  • Source quote: 따라서 아래 §결정/§Decision Evidence Map 의 셀은 조사 완료 전까지 RESEARCH_PENDING 으로 둔다 — 추측 금지(CLAUDE.md §11).
  • Source line: 39
  • Severity: High
  • Claim: Clean Architecture의 strict read path (QueryUseCase -> Read Repository -> Domain Entity) 우회가 아키텍처적 정합성을 깨뜨리지 않고 언제 허용되는지가 규정되어 있다.
  • Assumptions: branch note가 status_label: in-progress이고 2026-06-04 생성된 이후 외부 조사(wiki-decision-researcher)를 통한 aggregate bypass 원칙 합의가 없어도 skeleton의 read path를 안전하게 구현할 수 있다.
  • Failure mode: 개발자들이 대량 데이터 조회 성능 한계를 마주쳤을 때, 공식 가이드라인 부재로 인해 임의로 Use Case를 우회하여 controller가 직접 persistence adapter를 참조하거나, JPA entity graph를 presentation까지 leak하여 module boundary가 붕괴됨.
  • Falsification condition: strict read path를 무조건 강제하고, 어떠한 bypass도 허용하지 않음으로써 read performance overhead를 template 수준에서 완전히 감내하기로 결정한 경우.
  • Recommendation: wiki-decision-researcher subagent를 dispatch하여 through-aggregate vs read-model(native projection) vs thin read path (bypass usecase)의 아키텍처적 trade-off를 조사하고, B1~B3 validation gate와 context propagation(MDC)이 깨지지 않는 range 내에서 opt-in bypass criteria를 결정(D1, D2)하여 documentation을 완성할 것.
  • Verification command: grep -n "RESEARCH_PENDING" raw/branch-notes/feature-application-query-bypass-contract.md
  • Verification result: Line 39, Line 67, Line 71, Line 72 등 핵심 설계 결정 및 mapping matrix 전체가 RESEARCH_PENDING 상태로 미결 상태임이 확인됨.

L3-F05: ArchUnit static analysis의 reflection-style bean lookup (string-key bypass) 탐지 한계

  • Source file: raw/branch-notes/feature-architecture-enforcement-rules.md
  • Source quote: D11/D12 string-key bypass (알려진 한계, 보완 불가): D11 banned-class rule 은 class-literal getBean(Class<T>) 까지만 catch 한다.
  • Source line: 197
  • Severity: Medium
  • Claim: application_does_not_depend_on_application_context ArchUnit rule을 통해 application 계층이 Spring DI container와 Spring internal API에 결합되는 것을 철저히 방지한다.
  • Assumptions: runtime environment에서 string-key lookup을 악의적/실수 수준으로 우회하여 framework dependency를 application-core에 주입하더라도 boundary cleaniness가 깨지지 않는다.
  • Failure mode: 개발자가 custom class loader나 Spring bean factory string lookup을 사용하여 class-literal static rules를 우회하고, runtime에 framework-specific components to dynamic link하여 application purity를 훼손함.
  • Falsification condition: runtime application context scan (Spring Boot Actuator beans audit) 또는 compile-time validation check없이 manual code review만으로 전수 boundary check가 가능한 경우.
  • Recommendation: Spring context loading 시점에 application-core package 내의 class들이 dynamic bean lookup을 수행하는 것을 감시하기 위해, development/test profile에서 Spring BeanPostProcessorBeanFactoryPostProcessor를 커스텀 구현하여 application-core package domain 내 target bean lookup/resolution invocation을 intercept 및 block하는 runtime enforcement gate를 보완할 것.
  • Verification command: grep -n "string-key bypass" raw/branch-notes/feature-architecture-enforcement-rules.md
  • Verification result: Line 15, Line 101, Line 154, Line 197에 ArchUnit static analysis가 string-key lookup과 reflection bypass를 잡지 못하는 구조적 한계가 있음을 명시하고 있음.

L3-F06: Graceful shutdown 시 scheduler thread의 in-flight job 유실 및 duplicate processing risk

  • Source file: raw/branch-notes/feature-background-job-async-contract.md
  • Source quote: graceful shutdown = executor await termination ≤ 19s (container-runtime의 app shutdown 20s 내부에서 1s cleanup margin 확보.
  • Source line: 91
  • Severity: High
  • Claim: 19초 이내 graceful shutdown이 완료되면 thread pool 내에 적재된 background job들이 강제 종료되지 않고 transaction safety하게 보존된다.
  • Assumptions: container termination grace period (20s) 내에 active execution 중이던 transactional outbox publisher job이 강제 interrupt되더라도 DB lock이나 data recovery mechanism이 event duplicated publishing을 방지한다.
  • Failure mode: long-running background task가 19s 내에 종료되지 않아 SIGKILL에 의해 hard-terminated되거나, thread pool queue에 대기 중이던 job들이 serialization 없이 memory drop되어 job lost 발생. outbox의 경우 retry-on-startup lock timeout에 걸려 stale lock 상태가 지속됨.
  • Falsification condition: 모든 background job이 sub-second 단위로 실행되어 timeout 발생 가능성이 전혀 없거나, data loss가 도메인 비즈니스상 무관한 경우.
  • Recommendation: transaction outbox table에 job state(LOCKED/PENDING)와 lock TTL(Lease time)을 명시하여 shutdown 시 interrupt된 job을 startup context listener에서 auto-recovery(unlock)할 수 있도록 DB level lease contract를 공식화할 것.
  • Verification command: grep -n "graceful shutdown" raw/branch-notes/feature-background-job-async-contract.md
  • Verification result: Line 91, Line 106, Line 118, Line 169에서 19s graceful shutdown boundary만 명시했을 뿐, task interruption 시 outbox lock/job recovery 정책이 구체화되지 않음.

L3-F07: @Valid cascade depth limit (B4-2)에 대한 ArchUnit 정적 검사 rule 미완성

  • Source file: raw/branch-notes/feature-boundary-validation-mapping-contract.md
  • Source quote: planned (현 패스에서 nested DTO sample 부재로 ArchUnit 동적 검사 미작성 — cascade depth 컨벤션은 adapter-web/CLAUDE.md 에 문서화.)
  • Source line: 302
  • Severity: Medium
  • Claim: DTO validation cascade depth가 3단계 이하로 정적 제한되어 recursive object parsing 공격(DoS)을 사전에 차단한다.
  • Assumptions: adapter-web/CLAUDE.md에 기재된 manual cascade depth 컨벤션만으로 deep nested DTO mapping을 통한 validation stack overflow를 효과적으로 제어할 수 있다.
  • Failure mode: 개발자가 manual check 누락으로 web adapter dto segment에 4단계 이상의 @Valid nested graph를 도입하고, 이는 DoS 공격자가 CPU/Memory resources를 고갈시켜 server crash를 유발하는 entrypoint가 됨.
  • Falsification condition: web mapping layer에서 nested validation depth를 3단계 이상으로 확대해도 system overhead나 memory safety risk가 전혀 없는 경우.
  • Recommendation: web adapter package (..adapter.web..dto..) 내 모든 @Valid 필드를 재귀적으로 스캔하여 depth를 계산하는 custom ArchUnit static rule을 CleanArchitectureTest에 추가하고, nested structure sample을 portfolio DTO에 의도적으로 유입시켜 validation gate가 regression test로 동작하도록 반영할 것.
  • Verification command: grep -n "cascade depth" raw/branch-notes/feature-boundary-validation-mapping-contract.md
  • Verification result: Line 97, Line 202, Line 302에서 cascade depth <= 3 정적 검사가 sample DTO 부재로 인해 planned 상태에 머물러 있음이 확인됨.

L1-F08: Reproducible build에 대한 automated regression check (verification CI step) 누락

  • Source file: raw/branch-notes/feature-build-release-supply-chain-contract.md
  • Source quote: reproducibility 검증: 동일 commit 2회 build → artifact hash 불일치 시 fail.
  • Source line: 130
  • Severity: Medium
  • Claim: archives.preserveFileTimestamps=falsearchives.reproducibleFileOrder=true 설정으로 동일 커밋 빌드 시 항상 동일한 binary hash를 가지는 artifact가 보장된다.
  • Assumptions: CI/CD runner 환경과 local build 환경 간의 path delimiter, line separator, JDK vendor/minor version 차이가 bytecode metadata entropy를 유발하지 않으며, 이를 검증하는 CI task 없이도 보장된다.
  • Failure mode: 빌드 환경의 dynamic resource encoding, compile temporal metadata, 혹은 packaging library order의 미세한 차이로 인해 release artifact의 checksum이 달라져, supply chain validation (provenance verification)이 production deploy phase에서 깨짐.
  • Falsification condition: checksum verification이 build level에서 automated check 없이도 build lifecycle 동안 단 한 번의 hash collision이나 mismatch 없이 동작함을 보장할 수 있는 경우.
  • Recommendation: local profile 또는 CI pipeline 내에 verifyBuildReproducibility Gradle task를 추가하여, 동일한 project repository를 clean checkout한 후 2회 연속 빌드하여 artifact jar checksum diff를 대조하는 automated gate를 강제할 것.
  • Verification command: grep -n "reproducibility" raw/branch-notes/feature-build-release-supply-chain-contract.md
  • Verification result: Line 77, Line 130, Line 165에서 reproducibility 요구 및 hash verification test contract는 규정되어 있으나, 이를 자동 수행하는 build script task나 pipeline definition이 누락된 상태로 needs-confirmation에 머물러 있음.

L3-F09: Persistence integrity exception 핸들러 누락으로 인한 raw exception 누출 위험

  • Source file: raw/branch-notes/feature-business-rule-validation-contract.md
  • Source quote: persistence integrity 핸들러 미구현 확인 — GlobalExceptionHandler 에 DataIntegrityViolationException 핸들러 없음. owner feature-persistence-failure-baseline(documented-only).
  • Source line: 61
  • Severity: High
  • Claim: Database unique constraint, check constraint, check null violation 등 DB 레벨에서 발생하는 integrity failure가 raw state나 database query spec, constraint name을 클라이언트에 노출하지 않고 envelope 형식으로 안전하게 마스킹된다.
  • Assumptions: feature-persistence-failure-baseline 브랜치가 미구현 상태더라도 runtime exception이 upstream으로 leak되어 raw stack trace가 API consumer에게 leak될 위험을 dynamic error filter가 차단하고 있다.
  • Failure mode: DB layer에서 unique/foreign key constraint crash가 발생했을 때, Spring이 던진 DataIntegrityViolationException을 잡는 handler가 없어서 fallback으로 HTTP 500 INTERNAL_SERVER_ERROR와 함께 raw postgres/mysql error string ("duplicate key value violates unique constraint 'uk_worklog_date_title'")이 API response로 그대로 노출되어 infra structure 정보가 leak됨.
  • Falsification condition: GlobalExceptionHandlerException.class catch-all handler가 있고, 이것이 모든 response를 safety masking 처리하고 있어 details leak이 근본적으로 차단되고 있는 경우.
  • Recommendation: feature-persistence-failure-baseline 브랜치 구현을 선행 완료하거나, 본 baseline exception handling scope 내에 DataIntegrityViolationExceptionConstraintViolationException (JPA/DB) 핸들러를 임시 스캐폴딩하여 GlobalExceptionHandler가 이를 catch해 DATA_INTEGRITY 또는 CONFLICT category envelope로 변환하고 client-safe message만 응답하도록 덮어쓸 것.
  • Verification command: grep -n "DataIntegrityViolationException" raw/branch-notes/feature-business-rule-validation-contract.md
  • Verification result: Line 61 및 Line 251에서 DataIntegrityViolationException 핸들러가 현재 skeleton에 누락되었으며, 이로 인해 persistence integrity 매핑 테스트가 planned 상태에 멈춰 있음이 입증됨.


4.19 ### (Status: READ_FULL)

  • Source file: raw/branch-notes/feature-domain-feature-onboarding-contract.md
  • Source quote: only if new skeleton-wide response/error/header/log/metric/registry contract is required | domain-specific type, business enum, feature-specific DTO
  • Source line: 104
  • Severity: Medium
  • Claim: shared-contract에 개별 도메인 타입의 진입은 금지되나, 여러 features 간 공유가 필요한 공통 비즈니스 타입/Enum의 처리 가이드가 부재하여 도메인 간의 강한 결합이나 중복이 발생합니다.
  • Assumptions:
    1. 프로젝트 전반에서 공유되어야 하는 도메인 공통 개념(예: Address, Money 등)이 다수의 feature 모듈에 걸쳐 존재한다.
      • 무효 조건: 모든 feature가 완전히 독립적이고 공통되는 비즈니스 데이터 모델이 존재하지 않는다.
      • 사용자 검증 방법: domain-core 모듈 내 features 패키지 간 중복되는 데이터 클래스 존재 여부 체크
    2. multi-module Hexagonal 구조 하에서 도메인 간 직접 참조를 방지하는 컴파일 수준의 모듈 제어가 필요하다.
      • 무효 조건: Gradle 모듈이 분리되어 있지 않고 단일 모듈 내에서 package 경계만으로 결합을 관리한다.
      • 사용자 검증 방법: features/ 모듈 하위의 build.gradle dependency 선언 체크
  • Failure mode: features 간에 공통 비즈니스 enum(예: WorkLogStatus)을 서로 복사하여 중복 코드가 늘어나거나, 한 feature 모듈이 다른 feature 모듈을 직접 implementation하여 순환 참조와 강한 결합이 유발되어 multi-module의 경계가 붕괴됩니다.
  • Falsification condition: features 간에 공통으로 참조해야 하는 도메인 성격의 타입이 전무하고 완전히 독립적인 sub-domain들로만 구성되는 경우 비판이 무효화됩니다.
  • Recommendation: shared-contract 대신 domain-core 모듈 하위에 domain-shared 패키지 또는 모듈을 두어, 여러 feature가 공통으로 의존하는 비즈니스 VO 및 Enum을 독립적으로 수용할 수 있는 중간 경계를 설계하고 가이드에 명시하십시오.
  • Verification command: grep -n "shared-contract" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-domain-feature-onboarding-contract.md
  • Verification result: 104: | shared-contract | normally no change | only if new skeleton-wide response/error/header/log/metric/registry contract is required | domain-specific type, business enum, feature-specific DTO |

L3-F02: default package-private mutator의 Multi-Module Hexagonal 환경 내 컴파일 접근 제한 결함

  • Source file: raw/branch-notes/feature-domain-modeling-guardrails.md
  • Source quote: aggregate mutation 검사: @AggregateRoot annotation이 붙은 class의 모든 mutator method (*set* prefix 또는 state-changing method)는 (a) public이 아닌 package-private 또는 protected이고
  • Source line: 124
  • Severity: High
  • Claim: Hexagonal architecture에서 domain-core와 application-core는 물리적으로 패키지나 모듈이 분리되어 있어, domain aggregate의 mutator가 package-private으로 설정되면 application use case에서 이를 변경하기 위한 호출이 컴파일 에러를 일으키게 됩니다.
  • Assumptions:
    1. Java 언어의 default/package-private 접근 제어자가 다른 패키지에 정의된 application use case의 호출을 제한한다.
      • 무효 조건: domain과 application 코드가 같은 패키지 내에 선언되어 있거나, Kotlin의 internal 등 다른 가시성 스펙을 프로젝트 전반에 적용한다.
      • 사용자 검증 방법: domain entity의 mutator 정의 패키지와 application service 패키지 불일치 여부 체크
  • Failure mode: application use case가 domain aggregate의 invariant 검증이 포함된 mutator를 직접 호출하지 못하고, 이를 우회하기 위해 reflection을 사용하거나 domain layer에 강제로 public mutator를 열어 설계 가이드라인이 깨집니다.
  • Falsification condition: domain-core와 application-core가 단일 Gradle 모듈 및 동일 package schema 내에 속해 있어 package-private 접근이 가능한 구조라면 본 비판은 무효화됩니다.
  • Recommendation: mutator의 가시성을 단순히 package-private으로 강제하기보다는, application core가 위치한 application-core 모듈로의 접근을 허용하는 public interface 구조를 분리하거나, ArchUnit rule을 통해 'application layer 이외의 외부 adapter layer가 mutator를 호출하는 것'만 정적으로 제한하도록 ArchUnit 룰을 수정하십시오.
  • Verification command: grep -n "package-private" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-domain-modeling-guardrails.md
  • Verification result: 124: - aggregate mutation 검사: @AggregateRoot annotation이 붙은 class의 모든 mutator method (*set* prefix 또는 state-changing method)는 (a) public이 아닌 package-private 또는 protected이고 (b) invariant 검증 로직 포함.

L3-F03: APP_MULTI_INSTANCE_ENABLED 단일 플래그의 일괄 결합(All-or-Nothing)으로 인한 유연성 결여

  • Source file: raw/branch-notes/feature-env-driven-runtime-configuration.md
  • Source quote: multi-instance claim parsing 메커니즘 = env property APP_MULTI_INSTANCE_ENABLED boolean (default false).
  • Source line: 97
  • Severity: High
  • Claim: 분산 락, 캐시 보호, 아웃박스 리더 선출 등 이종 기술 요구사항을 단 하나의 global flag로 묶어서 강제함으로써, 특정 분산 컴포넌트만 선택적으로 켜고 끄는 마이크로 서비스별 세부 토폴로지 구성이 불가능해집니다.
  • Assumptions:
    1. 프로덕션 환경의 서로 다른 인스턴스/서비스군이 서로 다른 분산 아키텍처 토폴로지(예: 어떤 서비스는 캐시 스탬피드 방지가 불필요하나 분산 락은 필요함)를 필요로 한다.
      • 무효 조건: 모든 microservice가 예외 없이 5가지 분산 컴포넌트를 동시에 사용해야만 기동된다.
      • 사용자 검증 방법: application.yml 내 Redis/ShedLock 의존 관계 확인
  • Failure mode: 특정 환경에서 분산 캐시나 rate limiter를 사용할 수 없는 경우(예: Redis 장애 또는 특정 망 분리 환경), 전체 multi-instance 설정을 꺼야 하므로 이와 무관한 Outbox publisher leader election이나 ShedLock까지 비활성화되어 배치 중복 실행 등의 2차 장애가 발생합니다.
  • Falsification condition: 모든 배포 클러스터 노드가 단일한 인프라(Redis 등)를 상시 공유하며, 각 컴포넌트가 하나의 logical block으로 묶여 작동하는 독립 모놀리스 구조라면 무효화됩니다.
  • Recommendation: APP_MULTI_INSTANCE_ENABLED를 최상위 마스터 스위치로 유지하되, 각 컴포넌트별로 granular하게 제어할 수 있는 sub-flags(예: APP_DISTRIBUTED_LOCK_ENABLED, APP_CACHE_STAMPEDE_PROTECTION_ENABLED 등)를 도입하고 개별 override를 허용하도록 설계를 수정하십시오.
  • Verification command: grep -n "APP_MULTI_INSTANCE_ENABLED" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-env-driven-runtime-configuration.md
  • Verification result: 97: - 2026-05-22: multi-instance claim parsing 메커니즘 = env property APP_MULTI_INSTANCE_ENABLED boolean (default false).

L3-F04: 임시 파일 Orphan Cleanup의 Startup 시점 한정으로 인한 롱러닝 서버 리소스 고갈 취약점

  • Source file: raw/branch-notes/feature-file-resource-handling-contract.md
  • Source quote: temp file cleanup trigger = (1) success/failure on close (try-with-resources), (2) startup sweeper for orphaned files older than 1h, (3) JVM shutdown hook은 backup.
  • Source line: 89
  • Severity: High
  • Claim: 임시 파일의 orphan cleanup이 애플리케이션 'startup' 시점에만 1회성으로 트리거되어, 오랫동안 재시작 없이 켜져 있는 롱러닝 프로덕션 서버의 경우 임시 파일 누적으로 인한 디스크 고갈 리스크에 노출됩니다.
  • Assumptions:
    1. 서버 인스턴스의 기동 시간(Uptime)이 며칠에서 몇 달 동안 장기 유지된다.
      • 무효 조건: 서버가 하루에도 수십 번씩 무작위로 배포/재기동되거나 Serverless FaaS 환경에서 구동된다.
      • 사용자 검증 방법: kubectl get pods 등의 pod uptime 체크
    2. 예기치 않은 스트리밍 다운로드 장애나 try-with-resources 예외 처리 실패로 인한 임시 파일 leak이 프로덕션 환경에서 주기적으로 발생한다.
      • 무효 조건: 파일 업로드/다운로드 과정에서 단 하나의 임시 파일 누수도 일어나지 않는다.
      • 사용자 검증 방법: /tmp 디렉토리의 파일 수 증가 추이 관측
  • Failure mode: 프로덕션 서버 기동 중 비정상 종료된 업로드의 임시 파일들이 /tmp에 계속 쌓이다가 디스크 용량 한도(100퍼센트)에 도달하여, 다른 정상적인 디스크 I/O 작업(로그 생성, JVM GC dump 등)이 모두 차단되고 전체 컨테이너가 다운되는 장애가 발생합니다.
  • Falsification condition: 주기적인 K8s pod liveness probe 등으로 인해 1시간 이내에 무조건 pod가 재시작되어 startup sweeper가 실시간으로 작동하는 구조라면 무효화됩니다.
  • Recommendation: startup sweeper 외에 Spring @Scheduled 또는 background execution thread를 기반으로, 1시간 주기로 /tmp 디렉토리를 주기적으로 검사하여 생성된 지 1시간이 넘은 orphan temp file을 강제 정리하는 cron-like daemon task를 추가하십시오.
  • Verification command: grep -n "temp file cleanup trigger" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-file-resource-handling-contract.md
  • Verification result: 89: - 2026-05-22: temp file cleanup trigger = (1) success/failure on close (try-with-resources), (2) startup sweeper for orphaned files older than 1h, (3) JVM shutdown hook은 backup. file >1h not closed → orphan.

L3-F05: Readiness Scorecard의 All-or-Nothing binary 판정 방식의 실무 경직성과 우회 부작용

  • Source file: raw/branch-notes/feature-implementation-readiness-scorecard.md
  • Source quote: Readiness framing is binary pass/fail. any Fail = Not ready.
  • Source line: 147-148
  • Severity: Medium
  • Claim: 15개 영역에 걸친 아키텍처 규칙 전체가 통과해야만 readiness pass를 부여하는 All-or-Nothing 설계는 초기 MVP 개발 단계나 로컬 PoC 단계에서 불필요한 게이트 체증을 유발하고, 개발팀이 검증을 우회하려는 성향을 촉진합니다.
  • Assumptions:
    1. 프로젝트 초기 개발 또는 신규 feature 온보딩 단계에서 모든 부수 사안(trace propagation, security logging 등)을 즉각적이고 완전하게 구성하기는 어렵다.
      • 무효 조건: 개발 시작 시점부터 15개 영역의 모든 설정과 모듈이 이미 자동으로 세팅되어 구동된다.
      • 사용자 검증 방법: local 환경의 readiness scorecard pass 비율 점검
  • Failure mode: 로컬 개발 환경에서 빠르게 도메인 로직을 검증하고자 하는 상황에서도 readiness scorecard 실패로 인해 빌드/로컬 실행이 차단되자, 개발자들이 ArchUnit rule이나 verification task 자체를 @Disabled 처리하거나 임시로 mock을 끼워 넣어 아키텍처 게이트 자체의 실효성이 훼손됩니다.
  • Falsification condition: 모든 신규 기능 개발팀이 고도로 자동화된 템플릿 제너레이터를 통해 15개 영역의 코드를 자동 생성하여 즉각 pass할 수 있는 완전 자동화 도구를 구비한 경우 무효화됩니다.
  • Recommendation: Readiness Scorecard를 'Blocking Gates'와 'Advisory Gates'로 이원화하거나, release profile(local, dev, prod)에 따라 local/dev 환경에서는 일부 영역의 Fail을 허용하는 점진적 성숙도(Maturity scoring) 모드를 옵션으로 도입하십시오.
  • Verification command: grep -n "Readiness = Pass only" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-implementation-readiness-scorecard.md
  • Verification result: 147: Readiness = Pass only if every area is Pass.

L3-F06: 어댑터 비활성화(Disabled) 시 Use Case 계층 주입 컴파일/런타임 bean 누락 결함

  • Source file: raw/branch-notes/feature-integration-adapter-templates.md
  • Source quote: Spring @ConditionalOnProperty(name="app.adapter.{adapterName}.enabled", havingValue="true") 적용.
  • Source line: 90
  • Severity: High
  • Claim: @ConditionalOnProperty에 의해 어댑터 bean이 등록되지 않았을 때(enabled=false), 이를 의존성 주입(DI)받아 사용하는 application use case가 존재하는 경우 DI 실패(NoSuchBeanDefinitionException)로 인해 애플리케이션 시작 자체가 불가능해지는 설계적 모순이 발생합니다.
  • Assumptions:
    1. application use case 계층의 서비스 클래스들이 outbound port interface에 직접 컴파일 타임 의존성을 맺고, Spring DI(@Autowired 등)를 통해 주입받는다.
      • 무효 조건: use case 계층이 어댑터의 유무를 dynamic lookup이나 null-safety 코드로 직접 처리하고 있다.
      • 사용자 검증 방법: Application class의 startup run 시도 시 NoSuchBeanDefinitionException 발생 여부 체크
  • Failure mode: 특정 환경에서 Redis나 Kafka 어댑터를 끄기 위해 property를 false로 두었을 때, 관련 Use Case 빈들이 주입할 빈을 찾지 못해 startup fail-fast가 발생함으로써, 어댑터 비활성화 시 기능 토글 목적이 아예 달성되지 못합니다.
  • Falsification condition: 어댑터가 비활성화되면 관련 Use Case 빈들 전체도 Spring profile이나 conditional에 의해 자동으로 기동 범위에서 함께 탈락하도록 모듈 단위 게이팅이 적용된다면 무효화됩니다.
  • Recommendation: 어댑터 비활성화(enabled=false) 시, 해당 interface의 NoOp Null Object 구현체 또는 local stub bean을 default fallback(예: @ConditionalOnMissingBean)으로 등록해주는 auto-configuration 가이드를 보강하십시오.
  • Verification command: grep -n "havingValue" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-integration-adapter-templates.md
  • Verification result: 90: - Layer 1 (startup, runtime): Spring @ConditionalOnProperty(name="app.adapter.{adapterName}.enabled", havingValue="true") 적용. flag false 시 adapter bean 등록 X. ApplicationContext에 해당 bean 0개 verify.

L3-F07: Keycloak Account REST API 직접 호출 방식의 XSS 노출 및 불필요한 토큰 권한 확대 리스크

  • Source file: raw/branch-notes/feature-keycloak-account-linking-spa-ux.md
  • Source quote: SPA의 link 상태 표시 = Keycloak Account REST API 호출 — backend 거치지 않고 SPA가 직접. 이유: backend 코드 추가 0 (P2A와 동일하게 유지).
  • Source line: 103
  • Severity: High
  • Claim: SPA 브라우저 환경에서 직접 Keycloak Account REST API를 호출하게 만듦으로써, XSS 발생 시 해커가 사용자 계정을 unlink하거나 타 소셜 ID로 마음대로 link하는 등의 세션 탈취 리스크가 잔존하며, access token에 불필요한 account 관리 권한을 열어주어야 합니다.
  • Assumptions:
    1. 클라이언트 애플리케이션(SPA) 내에 임의의 서드파티 스크립트 실행으로 인한 XSS 공격 벡터가 상존한다.
      • 무효 조건: Content Security Policy(CSP) 및 sanitization을 통해 XSS 가능성을 실무적으로 완전 배제하고 있다.
      • 사용자 검증 방법: SPA token decode 시 aud claim 내 account 존재 여부 확인
  • Failure mode: SPA가 XSS 공격에 뚫렸을 때, 공격자가 획득한 access token의 account 권한을 이용해 Keycloak Account API를 임의 호출하여 사용자의 소셜 연동을 해제하고 공격자의 Google 계정을 연동(Account takeover)하는 시나리오가 활성화됩니다.
  • Falsification condition: SPA에 access token이 전달되지 않고 session cookie로만 세션을 유지하거나, Account API 자체가 client-side 호출을 차단하는 정책을 갖는다면 무효화됩니다.
  • Recommendation: SPA가 Keycloak Account REST API를 직접 통신하는 설계를 피하고, BFF 또는 backend application server가 Keycloak Admin API(또는 backchannel)를 이용해 백엔드 간 통신(M2M)으로 연동 상태를 확인 및 조작하는 간접 보안 경계를 구축하십시오.
  • Verification command: grep -n "SPA의 link 상태" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-keycloak-account-linking-spa-ux.md
  • Verification result: 103: - 2026-05-25: SPA의 link 상태 표시 = Keycloak Account REST API 호출 — backend 거치지 않고 SPA가 직접. 이유: backend 코드 추가 0 (P2A와 동일하게 유지). Account API audience(account)가 SPA token에 자동 포함되는지 확인 필요 (needs-confirmation).

L3-F08: Keycloak Identity Provider Sync Mode IMPORT 채택으로 인한 실무 Data Drift 결함

  • Source file: raw/branch-notes/feature-keycloak-account-linking-sub-vs-email.md
  • Source quote: Sync Mode = IMPORT (first login만). Google 측 email 변경이 Keycloak으로 자동 전파되지 않음 → Scenario B 회피.
  • Source line: 118
  • Severity: High
  • Claim: 소셜 계정의 email 변경 시 Keycloak으로 정보가 업데이트되지 않도록 IMPORT 모드로 고정하는 정책은, Google Workspace 등에서 소셜 email이 변경되었을 때 Keycloak 내부의 user email 정보가 점차 stale해져 다른 시스템과의 sync가 어긋나는 data drift 문제를 유발합니다.
  • Assumptions:
    1. 사용자의 Google Workspace 등 소셜 identity의 primary email이 시간이 지나면서 변경될 수 있다.
      • 무효 조건: 모든 사용자의 Google email은 가입 후 탈퇴 시까지 변경되지 않는다.
      • 사용자 검증 방법: Google Workspace Admin에서 이메일 변경 이력 조사
    2. 시스템 내부적으로 user.email 값을 활용해 비즈니스 알림 발송이나 타 시스템(CRM, ERP 등) 연동을 수행한다.
      • 무효 조건: 시스템이 user.email을 식별자 외의 용도로 사용하지 않는다.
      • 사용자 검증 방법: core 로직 내 email 발송 모듈 참조 범위 확인
  • Failure mode: 사용자가 소셜 email을 변경했음에도 Keycloak 및 서비스 DB 내 email은 과거 값(IMPORT 모드로 인해 고정됨)으로 남아 있어, 중요한 결제 완료 메일이나 비밀번호 변경 확인 메일 등이 과거 email 주소로 잘못 발송되거나, 타 시스템으로의 동기화가 실패하게 됩니다.
  • Falsification condition: 이메일을 단지 최초 가입 시의 가상 식별 용도로만 쓰고 비즈니스적으로 발송이나 연동에 사용하지 않는다면 무효화됩니다.
  • Recommendation: Sync Mode를 단순히 IMPORT로 고정하는 대신, sub를 invariant unique key로 유지하면서도, 로그인 시 이메일 정보는 최신 값으로 동기화하는 custom mapper 로직을 구현하거나, email 변경 감지 시 사용자에게 변경 확인 메일을 발송하고 Keycloak 내 email 속성을 갱신하는 event listener를 설계하십시오.
  • Verification command: grep -n "Sync Mode = IMPORT" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-keycloak-account-linking-sub-vs-email.md
  • Verification result: 118: - 2026-05-25: Sync Mode = IMPORT (first login만). Google 측 email 변경이 Keycloak으로 자동 전파되지 않음 → Scenario B 회피.

L3-F09: 보안 취약한 SPA Direct (P2A) 1순위 학습 채택으로 인한 엔터프라이즈 아키텍처 보안 결여

  • Source file: raw/branch-notes/feature-keycloak-bff-vs-spa-direct.md
  • Source quote: 본 keycloak-patterns 프로젝트는 SPA Direct (P2A)를 학습 목적의 1순위로 채택. BFF는 비교 문서로만 정리. 이유: canonical OIDC + PKCE 흐름을 먼저 이해하는 것이 목표.
  • Source line: 138
  • Severity: Medium
  • Claim: 브라우저 JS 메모리에 토큰이 노출되는 취약점을 가진 SPA Direct 방식을 템플릿의 기본 아키텍처로 우선 채택함으로써, 엔터프라이즈 환경에서 보안을 강화하기 위한 BFF 설계 자산과 베스트 프랙티스(AutoConfiguration 등) 확보가 누락됩니다.
  • Assumptions:
    1. ca-tmpl 템플릿을 차용하여 실제 프로덕션 서비스를 구축하는 조직들이 금융, 의료, e-commerce 등 고보안 요건을 가지고 있다.
      • 무효 조건: ca-tmpl은 내부 토이 프로젝트 및 단순 교육용으로만 제한적으로 사용된다.
      • 사용자 검증 방법: 템플릿의 주요 적용 프로젝트 보안 요구도 조사
  • Failure mode: 템플릿이 SPA Direct만 프로덕션 레벨로 제공하므로, 신규 프로젝트 구축 시 개발팀이 이를 그대로 복사하여 사용하다가, 차후 보안 감사에서 브라우저 내 토큰 노출(LocalStorage 등)이 지적되어 대대적인 아키텍처 개편(BFF 전환)을 겪으며 비용이 낭비됩니다.
  • Falsification condition: SPA Direct 구조에서 XSS 공격이 일어날 수 없도록 보장하거나, Refresh Token Rotation과 엄격한 Cookie 관리를 통해 SPA Direct의 보안성을 BFF 수준으로 자동 격상시킬 수 있다면 무효화됩니다.
  • Recommendation: BFF 실 구현 패키지를 ca-skeleton의 공식 서브 모듈 또는 profile-driven optional 모듈로 통합하여 제공하고, 보안 민감도에 따라 SPA Direct와 BFF를 설정 스위치 하나로 전환할 수 있는 ready-to-use 아키텍처 템플릿으로 보강하십시오.
  • Verification command: grep -n "BFF는 비교 문서" /home/donghyeon/dev/llm-wiki-private/raw/branch-notes/feature-keycloak-bff-vs-spa-direct.md
  • Verification result: 138: - 2026-05-25: 본 keycloak-patterns 프로젝트는 SPA Direct (P2A)를 학습 목적의 1순위로 채택. BFF는 비교 문서로만 정리. 이유: canonical OIDC + PKCE 흐름을 먼저 이해하는 것이 목표.


4.46 ### (Status: READ_FULL)

  • Source file: raw/branch-notes/feature-keycloak-public-domain-tunneling.md
  • Source quote: Cloudflare Tunnel 의 <UUID>.cfargotunnel.com generic subdomain 이 Google Cloud Console redirect URI 정책에 통과하는지는 CLOUDFLARE-TUNNEL-C4 "Does not prove" 단서에 명시된 미검증 사항 — P3B 실 검증 필요
  • Source line: 112
  • Severity: MEDIUM
  • Claim: Cloudflare Tunnel이 자동 발급하는 generic subdomain이 Google OAuth client console의 redirect URI validation 및 security filter를 무사히 통과할 것이다.
  • Assumptions: Google Cloud Console의 OAuth redirect URI 검증 엔진이 generic trycloudflare.com 혹은 cfargotunnel.com 도메인을 phishing/abuse 도메인으로 차단하지 않고 정상 허용한다.
  • Failure mode: Google Cloud Console이 generic tunnel subdomain에 대해 명시적 혹은 묵시적 도메인 평판 필터링을 가하여, redirect URI 등록이 거부되거나 런타임에 redirect_uri_mismatch를 내며 integration이 불가능해짐.
  • Falsification condition: Google Cloud Console에 <UUID>.cfargotunnel.com/keycloak/... 주소를 등록하려 할 때 "Domain invalid or untrusted" 등의 오류가 브라우저 콘솔 또는 UI에 발생함.
  • Recommendation: free generic subdomain에 의존하지 않고, Cloudflare DNS에 사용자가 제어하는 custom domain을 연결하여 CNAME을 터널에 매핑하는 정적 DNS 매핑 구성을 개발 규격서에 필수로 기재할 것.
  • Verification command: sed -n '112p' 'raw/branch-notes/feature-keycloak-public-domain-tunneling.md'
  • Verification result: | D1 | 학습 환경 1순위 **Cloudflare Tunnel** (named tunnel + 정적 URL + 무료 TLS + inbound port 0) | raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C1 (cloudflared outbound), raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C2 (firewall inbound 차단 권장), raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C3 (tunnel <UUID>.cfargotunnel.com subdomain 자동 부여), raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C4 (사용자 hostname CNAME → cfargotunnel.com), raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C2 (Google redirect URI raw IP 금지 → 도메인 필요) | official-vendor-doc + official-vendor-doc | Cloudflare Tunnel 의 <UUID>.cfargotunnel.com generic subdomain 이 Google Cloud Console redirect URI 정책에 통과하는지는 CLOUDFLARE-TUNNEL-C4 "Does not prove" 단서에 명시된 미검증 사항 — P3B 실 검증 필요 |

L6-F02: Keycloak Realm Import JSON 내 환경변수 동적 치환 설계 누락

  • Source file: raw/branch-notes/feature-keycloak-realm-client-export.md
  • Source quote: export JSON에서 secret/password 제거(또는 placeholder 치환) 후 git commit — 등급: planned
  • Source line: 88
  • Severity: LOW
  • Claim: Git repository에 JSON을 형상 관리할 때 secret을 제거한 뒤 manual 또는 simple replacement script(예: sed)를 통해 target client secret을 매번 주입하는 방식으로 pipeline reset을 수행할 수 있다.
  • Assumptions: Keycloak JSON parser가 import 시점에 raw string placeholder 외에 동적인 parsing 능력을 갖고 있지 않다.
  • Failure mode: template 치환 스크립트 작성 부담 가중 및 개발자 실수로 인해 Plaintext credential이 포함된 JSON이 Git repository에 오머지(bypass merge)되어 보안 사고 발생.
  • Falsification condition: Keycloak container 구동 시 --import-realm 명령이 JSON 내부의 ${env.CLIENT_SECRET_VAR} 구문을 native substitution하여 정상 파싱 처리해 주는 기능이 있는지 검증.
  • Recommendation: Keycloak Quarkus distribution의 built-in parsing 규격을 활용하여 JSON 내 credential 값을 ${env.KEYCLOAK_CLIENT_SECRET} 형식으로 매핑하고, docker-compose의 env block을 통해 runtime에 주입하도록 설계를 표준화할 것.
  • Verification command: sed -n '88p' 'raw/branch-notes/feature-keycloak-realm-client-export.md'
  • Verification result: - [ ] export JSON에서 secret/password 제거(또는 placeholder 치환) 후 git commit — 등급: planned

L6-F03: Concurrency Context에서 Refresh Token Rotation의 정상 세션 강제 종료 결함

  • Source file: raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md
  • Source quote: Max Reuse 0: 동시 재사용 1회도 허용 안 함. SPA가 race condition으로 같은 refresh token을 동시에 두 번 보내면 family kill — 학습 시연 시 단일 thread 보장.
  • Source line: 92
  • Severity: HIGH
  • Claim: Max Reuse = 0 설정 하에서 Refresh Token Rotation을 활성화하면 비동기 브라우저 SPA 환경에서 정상 사용자의 UX를 저해하지 않고 안정적으로 동작한다.
  • Assumptions: SPA application이 parallel API requests를 전송할 때 401 Unauthorized 헤더를 감지하여 토큰을 갱신하는 logic이 fully serialized 되어 한 번에 단 하나의 refresh call만 발생한다.
  • Failure mode: 멀티 탭 환경 혹은 비동기 컴포넌트가 대량 렌더링되면서 동시에 backend Resource Server API를 호출할 때, 거의 동시(millisecond 간격)에 여러 개의 401 갱신 요청(/token grant_type=refresh_token)이 전송됨. Keycloak이 첫 요청을 처리해 RT_1을 무효화하고 새 RT_2를 반환하지만, 동시에 인입된 두 번째 요청(동일 RT_1 사용)을 침해 공격으로 오판하여 token family 전체(RT_2 포함)를 무효화하여 정상 사용자가 즉시 로그아웃되는 오탐지(False Positive) 발생.
  • Falsification condition: SPA client가 async race context에서 overlapping /token refresh request를 보낼 때 token family invalidation이 즉시 트리거되는지 여부.
  • Recommendation: ca-tmpl SPA client code 내에 refresh token request를 단일 Lock Promise로 직렬화 처리해 주는 Serialized Token Refresh Interceptor 설계를 강제 명문화할 것.
  • Verification command: sed -n '92p' 'raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md'
  • Verification result: - **Max Reuse 0**: 동시 재사용 1회도 허용 안 함. SPA가 race condition으로 같은 refresh token을 동시에 두 번 보내면 family kill — 학습 시연 시 단일 thread 보장.

L6-F04: Refresh Token Rotation 도입 시 Keycloak Persistent Store Write 부하 증가 고려 누락

  • Source file: raw/branch-notes/feature-keycloak-refresh-token-rotation.md
  • Source quote: | D1 | refresh token rotation 활성화 (Revoke Refresh Token: ON + Refresh Token Max Reuse: 0) — reuse detection 으로 stolen token 탐지
  • Source line: 136
  • Severity: MEDIUM
  • Claim: Short access token TTL에 의한 refresh 트래픽 증가가 Keycloak persistence store(RDBMS)에 별도의 I/O 부하 문제를 일으키지 않을 것이다.
  • Assumptions: user session 갱신 시 DB write transactional overhead가 keycloak 성능 저하의 병목지점이 되지 않는다.
  • Failure mode: access token 수명을 5분 내외로 극히 단축하고 rotation을 켰을 때, 동시 사용자가 증가하면 매 5분마다 DB write transactional lock(UPDATE USER_SESSION / CLIENT_SESSION 테이블)이 폭증하여 DB thread pool 고갈 및 Keycloak API 지연 유발.
  • Falsification condition: rotation 비활성화 시와 활성화 시의 DB Write IOPS 및 TPS 증가 추이를 벤치마크 툴로 비교할 때 병목현상이 감지되는지 여부.
  • Recommendation: high-concurrency 시나리오에 ca-tmpl을 사용할 경우 RDBMS write load를 방어하기 위해 Keycloak clustering 및 memory-based dynamic user session cache(Infinispan) 설정을 최적화 가이드라인에 필수 등록할 것.
  • Verification command: sed -n '136p' 'raw/branch-notes/feature-keycloak-refresh-token-rotation.md'
  • Verification result: | D1 | refresh token rotation 활성화 (Revoke Refresh Token: ON+Refresh Token Max Reuse: 0) — reuse detection 으로 stolen token 탐지 | raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3 (refresh token = scope/resource server bound MUST) — rotation 자체는 OAuth 2.1 권고 배경; **Keycloak 의 정확한 UI 항목 라벨** 은 UNSUPPORTED_DECISION(citedkeycloak-securing-apps-overview-official의 C1~C3 은 protocol overview 수준이며 rotation/revoke UI 직접 명시 없음) |official-standard (rotation 권고) + UNSUPPORTED_DECISION (vendor UI 라벨)| Keycloak 25.x admin UI 라벨이 실제로Revoke Refresh Token/Refresh Token Max Reuse 인지 직접 verify 필요 |

L6-F05: path-prefix 라우팅(/keycloak/*) 하에서 Keycloak Base Path Rewrite Asset 404 에러

  • Source file: raw/branch-notes/feature-keycloak-reverse-proxy-headers.md
  • Source quote: - **2026-05-25**: path-prefix 라우팅(/keycloak/) 채택. 이유: 단일 EC2에 SPA(/) + API(/api/) + Keycloak(/keycloak/*)을 한 도메인에 묶기 위함. 부모 P3B 다이어그램과 일치.
  • Source line: 137
  • Severity: HIGH
  • Claim: Nginx 혹은 Caddy에서 /keycloak/* path를 Keycloak 포트로 forwarding하면, Keycloak admin console UI 및 discovery document endpoint가 별도 옵션 없이 정상 노출될 것이다.
  • Assumptions: Keycloak application container가 incoming URI prefix /keycloak을 default routing hierarchy와 매칭하도록 알아서 변환하거나 proxy layer의 rewrite가 알아서 자원을 복원할 수 있다.
  • Failure mode: reverse proxy가 prefix /keycloak을 strip하여 Keycloak에 보내거나, strip하지 않고 그대로 보냈을 때 Keycloak이 relative path를 인지하지 못해 static assets(CSS/JS)를 root /resources로 redirect → browser에서 404 Not Found가 발생하고 admin console 화면이 깨짐.
  • Falsification condition: KC_HTTP_RELATIVE_PATH=/keycloak 옵션을 인입시키지 않거나, proxy rewrite rule mismatch 상태에서 /keycloak/admin 호출 시 admin console 렌더링에 필요한 js/css 로드 실패가 발생하는지 검증.
  • Recommendation: ca-tmpl의 nginx/Caddy default config template에 반드시 KC_HTTP_RELATIVE_PATH=/keycloak 환경 변수를 한 쌍으로 설정하고 proxy routing block에서 path prefix preservation 설정을 명확히 적용할 것.
  • Verification command: sed -n '137p' 'raw/branch-notes/feature-keycloak-reverse-proxy-headers.md'
  • Verification result: - **2026-05-25**: path-prefix 라우팅(/keycloak/) 채택. 이유: 단일 EC2에 SPA(/) + API(/api/) + Keycloak(/keycloak/*)을 한 도메인에 묶기 위함. 부모 P3B 다이어그램과 일치.

L6-F06: Google IdP brokering 시 Account Linking 취약점(Account Hijacking)

  • Source file: raw/branch-notes/feature-keycloak-single-ec2-google-federation.md
  • Source quote: Keycloak first-broker-login flow 가 Account Linking 시 "비밀번호 확인 후 link" 정책으로 실제 작동
  • Source line: 243
  • Severity: CRITICAL
  • Claim: Google IdP user email을 Keycloak local user database와 matching하여 account mapping할 때 default brokering flow가 계정 탈취 위협을 안전하게 방어한다.
  • Assumptions: Google OAuth user가 local user와 linking될 때 Google이 전달한 email claim이 verified 상태임을 검증하고, Keycloak이 credential re-verification을 생략 없이 통제한다.
  • Failure mode: Keycloak first-broker-login flow에서 Confirm Link Existing Account 시 credential 검증(예: 기존 local user password 재확인)을 skip하고 email-match만으로 link할 때, 공격자가 local user와 동일한 email을 임의의 허위 Google 계정으로 생성하여 OIDC federation 로그인을 시도할 시 기존 local account 권한 전체를 hijacking하는 심각한 보안 사고 유발.
  • Falsification condition: Google client idp registration 상에서 email_verified 검증 옵션을 활성화하지 않고 Confirm Link Existing Account flow에서 password 재입력 필드를 누락 시 silent link가 발생하는지 여부.
  • Recommendation: ca-tmpl의 IdP brokering flow 정책에 First Broker Login flow customized copy 및 Require Password/OTP Verification을 force authenticator로 추가할 것을 권고하고, google mapping rules에 email_verified=true 검증을 custom mapper 레벨에서 명문화할 것.
  • Verification command: sed -n '243p' 'raw/branch-notes/feature-keycloak-single-ec2-google-federation.md'
  • Verification result: | Keycloak first-broker-login flow 가 Account Linking 시 "비밀번호 확인 후 link" 정책으로 실제 작동 | KC_FBL-C2 등이 needs-confirmation 등급 — 정책 UI 토글 위치/동작 불확실 | Admin UI → Authentication → First Broker Login → flow copy + Confirm Link Existing Account authenticator 추가 → 같은 email 의 local user 사전 생성 후 Google 로그인 시도 | needs-confirmation |

L6-F07: Custom local domain 사용 시 SubtleCrypto API Runtime Crash 가능성

  • Source file: raw/branch-notes/feature-keycloak-single-ec2-no-google.md
  • Source quote: - 2026-05-25: **vanilla JS는 manual fetch + crypto.subtle기반 PKCE 구현 우선**, 작동 확인 후oidc-client-ts로 마이그레이션 비교. 이유: OIDC lifecycle 내부 동작 학습.
  • Source line: 156
  • Severity: MEDIUM
  • Claim: vanilla JS client 내에서 호출하는 crypto.subtle API가 HTTP 학습 환경의 모든 호스트 및 IP 주소 하에서 안정적으로 호출될 수 있다.
  • Assumptions: user-agent(browser)가 custom local dev domain (예: http://my-keycloak.local)을 secure context로 간주하여 SubtleCrypto interface를 차단 없이 노출한다.
  • Failure mode: http://localhost가 아닌 custom hostname/IP로 접속한 local 개발 환경 브라우저에서 crypto.subtleundefined로 떨어져 PKCE challenge 생성 시 TypeError: Cannot read properties of undefined 에러와 함께 SPA 전체가 runtime crash를 일으킴.
  • Falsification condition: secure context 기준(localhost 또는 HTTPS)이 충족되지 않은 custom HTTP dev domain에서 SubtleCrypto API 호출 시 script crash가 감지되는지 확인.
  • Recommendation: plain HTTP custom host test 시 memory crypto polyfill fallback logic을 feature detection (if (!window.crypto || !crypto.subtle))으로 wrapping하여 bypass code를 마련하거나 SSL self-signed cert binding을 default로 명세할 것.
  • Verification command: sed -n '156p' 'raw/branch-notes/feature-keycloak-single-ec2-no-google.md'
  • Verification result: - 2026-05-25: **vanilla JS는 manual fetch + \crypto.subtle` 기반 PKCE 구현 우선**, 작동 확인 후 `oidc-client-ts`로 마이그레이션 비교. 이유: OIDC lifecycle 내부 동작 학습.`

  • Source file: raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff.md
  • Source quote: - 2026-05-25: P2A에서 권장 조합 = **access_token: 메모리 + refresh_token: secure httpOnly cookie**. 단 본 branch는 구현 없으므로 documented-only.
  • Source line: 112
  • Severity: CRITICAL
  • Claim: SPA Direct pattern(SPA가 Keycloak API를 direct request) 하에서, refresh_token을 secure httpOnly cookie로 client domain에 저장하여 cross-site script leakage를 방어할 수 있다.
  • Assumptions: Keycloak auth domain(keycloak.com)과 SPA client domain (spa.com)이 다른 cross-origin configuration 상태에서 browser sandbox가 keycloak token endpoint의 Set-Cookie header를 cross-origin context에서 차단 없이 load/send한다.
  • Failure mode: modern browsers의 3rd-party cookie restriction (Safari ITP / Chrome Phase-out) 정책에 의해 SameSite=None; Secure cookie가 block되어 SPA domain에서 Keycloak domain으로 cookie transmission이 전면 차단됨. refresh_token 갱신이 불가능해져 SPA authentication lifecycle이 fail함.
  • Falsification condition: client와 identity provider domain이 다른 direct auth topology에서 3rd-party cookie blocked 환경 테스트 시 request cookie transmission failure 발생.
  • Recommendation: browser direct token endpoint access token pattern을 sub-optimal로 밀어내고, SPA domain server side가 Keycloak call을 proxying하여 secure SameSite=Lax cookie session을 application layer에 공급하는 BFF(Backend For Frontend) 패턴을 ca-tmpl의 core architecture로 상향 지정할 것을 권장하십시오.
  • Verification command: sed -n '112p' 'raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff.md'
  • Verification result: - 2026-05-25: P2A에서 권장 조합 = **access_token: 메모리 + refresh_token: secure httpOnly cookie**. 단 본 branch는 구현 없으므로 \documented-only`.`

L6-F09: NimbusJwtDecoder의 Default JWKS Cache Stampede 취약점

  • Source file: raw/branch-notes/feature-keycloak-spring-rs-audience-validator.md
  • Source quote: prod에서는 JwkSetUriJwtDecoderBuilder.cache(Cache) 로 custom cache(Caffeine 등) 권장 — 학습 범위 외
  • Source line: 105
  • Severity: HIGH
  • Claim: NimbusJwtDecoder의 default 5분 cache configuration이 Keycloak client token verification load가 높거나 key rotation이 빈번한 multi-threaded context에서 cache stampede (JWKS endpoint overload) risk를 차단한다.
  • Assumptions: JWKS fetch request가 key validation fail 시 background single lock serialization으로 처리되어 external HTTP endpoint storming을 방지한다.
  • Failure mode: Keycloak에서 key rotation이 일어나거나, 악의적인 client가 임의의 invalid kid를 가진 JWT를 backend로 brute-force 요청할 때, backend JVM thread들이 kid cache-miss를 해소하기 위해 동시에 Keycloak JWKS endpoint (/certs)로 HTTP request를 cascading 전송하여 Keycloak에 network/thread exhaustion DDoS를 유발함.
  • Falsification condition: 존재하지 않는 invalid kid JWT를 concurrent resource server requests로 대량 발송 시, JWKS discovery endpoint로의 outbound HTTP query 수가 outbound query pool control 없이 requests 수에 비례하여 급증하는 현상 발생.
  • Recommendation: ca-tmpl backend template에 NimbusJwtDecoder 생성 시 default cache 대신, rate limiter가 적용된 custom JWKS loader 또는 concurrency lock이 구현된 cache manager (JwkSetUriJwtDecoderBuilder.cache with Guava/Caffeine and loading cache lock)를 integration하도록 baseline 설계를 보완하십시오.
  • Verification command: sed -n '105p' 'raw/branch-notes/feature-keycloak-spring-rs-audience-validator.md'
  • Verification result: - [ ] **JWKS cache 정책** — 등급: \documented-only`(105라인:prod에서는 JwkSetUriJwtDecoderBuilder.cache(Cache) 로 custom cache(Caffeine 등) 권장 — 학습 범위 외`)

4-1. Adversarial Review

  • Adversarial Review 1: L6-F08 (BFF 패턴 전환 권고에 대한 비판)

    • Counterargument: BFF 패턴 도입은 SPA Direct 구조에 비해 인프라 운영 부담(BFF proxy 서버 구축, session persistence 관리)이 과도하게 증가하며, stateless의 최대 장점인 무상태 수평 확장을 훼손한다.
    • Rebuttal / Architectural Mitigation: 3rd-party cookie 제약은 modern browser들의 거부할 수 없는 보안 강제 사항입니다. SPA Direct 하에서 httpOnly secure cookie를 cross-origin 도메인 간에 전달하려는 시도는 runtime 단계에서 브라우저 sandbox에 의해 전면 차단됩니다. 따라서 BFF 프록시 계층의 state를 stateless gateway session(예: encrypted client-side session cookie) 또는 Redis shared storage로 외재화하여 수평 확장을 유지하면서도 브라우저 보안 제약을 극복하는 방향이 ca-tmpl의 미래 지향성에 부합합니다.
  • Adversarial Review 2: L6-F03 (Refresh Token Rotation False Positive 비판)

    • Counterargument: 비동기 race condition으로 인한 로그아웃은 client-side library(oidc-client-ts 등)가 in-flight request를 잘 관리하면 예방할 수 있으므로, Keycloak의 Max Reuse = 0 설정을 완화(예: Max Reuse = 1 또는 2)하는 것이 아키텍처 복잡도를 낮추는 직관적 해법이다.
    • Rebuttal / Architectural Mitigation: Max Reuse 설정을 1 이상으로 완화하는 순간, 공격자가 탈취한 RT를 1회 사용하는 행위를 Keycloak이 "동시 요청 유예"로 파싱하여 침해 탐지 로직이 침묵하게 됩니다. 이는 보안 수준의 타협을 야기하므로, identity provider의 엄격한 보안 규칙을 유지하되 client layer에서 fetch promise caching / locking queue interceptor를 구현해 갱신 요청을 엄격히 직렬화하는 것이 올바른 아키텍처 설계 방향입니다.
  • Adversarial Review 3: L6-F09 (JWKS Cache Stampede 취약성 비판)

    • Counterargument: Spring Security NimbusJwtDecoder는 내부적으로 concurrency control을 가지고 있으며, JWKS endpoint query storming은 outbound firewall rate limiter 또는 API gateway 레벨에서 block하면 되므로 Resource Server 내부에 lock cache를 두는 것은 복잡도 오버헤드다.
    • Rebuttal / Architectural Mitigation: NimbusJwtDecoder의 기본 key resolver(JwkRetriever)는 unknown kid를 수신할 때마다 cache를 bypass하고 동기/비동기 outbound HTTP call을 트리거합니다. firewall/gateway가 이를 차단하더라도 Resource Server 내부의 request thread가 outbound call 대기 상태로 block되어 container worker thread pool 고갈이 발생합니다. JVM layer에서 invalid kid의 lookup rate를 rate-limit하고, loading cache lock을 통해 단 하나의 thread만 certs를 fetch하도록 강제하는 것이 reactive resiliency 및 core architecture 보안의 기본 소양입니다.


4.55 ### (Status: READ_FULL)

  • Source file: raw/branch-notes/feature-keycloak-spring-rs-role-mapping.md
  • Source quote: - [ ] JwtAuthenticationConverter 빈: realm_access.roles → SimpleGrantedAuthority("ROLE_" + role) 매핑 — 등급: planned
  • Source line: 82
  • Severity: High
  • Claim: Spring Security의 JwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles")를 지정하는 것만으로 Keycloak의 중첩(nested) JWT claim인 realm_access.roles 배열을 파싱하여 Spring Security role로 매핑할 수 있다는 설계 가정.
  • Assumptions:
    1. Keycloak이 발급한 JWT의 구조가 realm_access: { roles: [ "admin-role" ] } 형태로 들어올 것.
    2. Spring Security의 default JwtGrantedAuthoritiesConverter가 dot notation(예: "realm_access.roles")을 파싱해 내부 맵에 접근할 수 있을 것.
  • Failure mode: Spring Security의 JwtGrantedAuthoritiesConverter는 기본적으로 dot-notation이나 JSON path를 지원하지 않고 단일 flat key 매핑만 수행합니다. 따라서 "realm_access.roles"를 claim name으로 지정하면 Spring은 이를 문자 그대로 "realm_access.roles"라는 이름을 가진 flat String key로 찾으려고 하여 nested 구조를 파싱하지 못하고, 결국 어떠한 권한(Authority)도 획득하지 못해 모든 권한 검증 API가 403 Forbidden을 반환합니다.
  • Falsification condition: Keycloak의 realm_access.roles가 Custom Protocol Mapper를 통해 JWT 루트 레벨의 flat array(예: "roles": ["admin-role"])로 변환되어 인입되는 경우에는 해당 finding이 적용되지 않습니다.
  • Recommendation: JwtAuthenticationConverter 설정 시 JwtGrantedAuthoritiesConverter에 단순히 claim name만 세팅하는 대신, JWT에서 직접 Map 형태의 "realm_access"를 추출한 뒤 그 내부의 "roles" Collection을 파싱하여 Spring Authority(SimpleGrantedAuthority)로 가공해 반환하는 custom converter 람다식을 빈으로 구현하십시오.
  • Verification command: grep -nF 'realm_access.roles' raw/branch-notes/feature-keycloak-spring-rs-role-mapping.md
  • Verification result: 82: - [ ] JwtAuthenticationConverter 빈: realm_access.roles → SimpleGrantedAuthority("ROLE_" + role) 매핑 — 등급: planned

L7-F02: Keycloak Google JWKS 캐시 갱신 지연에 따른 인증 전면 장애 가능성

  • Source file: raw/branch-notes/feature-keycloak-three-leg-trust-chain.md
  • Source quote: 함정: Keycloak이 Google JWKS 캐시 갱신 실패 시 Google 로그인 전체 장애 → fallback 정책 확인 — 등급: needs-confirmation
  • Source line: 78
  • Severity: High
  • Claim: Google Identity Provider 연동 시, Google의 비정기적 JWKS(Json Web Key Set) 회전(Rotation)을 Keycloak이 유연하게 동기화하고 캐싱하여 검증을 수행할 것이라는 신뢰성 가정.
  • Assumptions:
    1. Google의 공개 키 서명용 JWKS Endpoint가 상시 가용하며 Keycloak이 이를 요청 시간 내에 조회할 수 있을 것.
    2. Google의 키 회전 빈도에 대응하여 Keycloak의 외부 IdP 키 캐시 정책이 무효화 후 강제 갱신(Cache Eviction) 메커니즘을 적절히 동작시킬 것.
  • Failure mode: Google이 새로운 서명 키로 ID Token을 발급하기 시작하는 시점에, Keycloak이 Google의 JWKS Endpoint 조회 과정에서 일시적인 네트워크 장애나 속도 제한(Rate Limit)을 겪으면 캐시가 갱신되지 못합니다. 이 경우 Keycloak은 기존 만료되거나 존재하지 않는 키로 서명된 Google ID Token 검증을 실패 처리하게 되며, 이에 따라 전체 Google 소셜 로그인 기능이 전면적인 502/503 오류로 정지되는 장애가 발생합니다.
  • Falsification condition: Google의 JWKS Endpoint 외에 사전에 다운로드된 정적 인증서 체인을 백업으로 사용하거나, Keycloak이 키 불일치 감지 시 즉시 동기 방식으로 fallback 조회하는 강한 복구 정책이 기본 탑재된 경우 본 finding은 우회됩니다.
  • Recommendation: Keycloak의 Identity Provider 설정에서 Use JWKS URL 정책과 함께 JWKS Cache TTL을 기본값보다 유연하게 단축하고, 외부 API 통신 실패 시 최대 3회 이내의 지수 백오프(Exponential Backoff) 재시도 로직을 가동하도록 네트워크 타임아웃 및 서킷 브레이커 설정을 튜닝하십시오.
  • Verification command: grep -nF 'Google JWKS 캐시 갱신 실패' raw/branch-notes/feature-keycloak-three-leg-trust-chain.md
  • Verification result: 78: - [ ] 함정: Keycloak이 Google JWKS 캐시 갱신 실패 시 Google 로그인 전체 장애 → fallback 정책 확인 — 등급: needs-confirmation

L7-F03: Traefik ForwardAuth의 Authorization 헤더 기본 전송에 따른 정보 유출 취약성

  • Source file: raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative.md
  • Source quote: default (empty) 가 Authorization 등 sensitive header 까지 인증 서버로 보내 production 위험 — 명시 화이트리스트 필요
  • Source line: 105
  • Severity: High
  • Claim: Traefik의 forwardAuth middleware를 설정할 때, authRequestHeaders 옵션을 명시하지 않더라도 기본 설정 수준에서 보안 문제가 유발되지 않을 것이라는 편의성 가정.
  • Assumptions:
    1. Traefik 뒤에 배치되는 인증용 서버(예: oauth2-proxy)가 신뢰 영역 내에 존재할 것.
    2. 클라이언트가 원래 백엔드로 전송하고자 했던 민감한 Authorization 또는 인증 쿠키 정보가 인증 프록시 서버로 유출되어도 무관할 것.
  • Failure mode: authRequestHeaders가 공백(default empty)으로 두어지면 Traefik은 원본 클라이언트 요청에 포함된 모든 HTTP 헤더(Authorization Bearer 토큰, 세션 쿠키 등)를 중간 인증 서버로 그대로 복사하여 전송합니다. 인증 서버가 만약 로깅 단계나 제3의 에러 리포팅 툴로 요청을 로깅한다면, 백엔드 전용 토큰이나 민감 세션 자격 증명이 인증 서버 측 로그에 고스란히 남아 심각한 토큰 탈취 및 자격 증명 유출 경로가 열리게 됩니다.
  • Falsification condition: 인증 게이트웨이와 인증 처리기가 동일한 메모리 영역 혹은 완전 격리된 동일 프로세스 신뢰 내부망에서만 동작하고 로그를 전혀 남기지 않는 것이 증명되는 상황이라면 위험도가 감소합니다.
  • Recommendation: Traefik forwardAuth middleware 선언 시 authRequestHeaders 목록을 공백으로 두지 말고, 인증에 필요한 필수적인 컨텍스트 헤더(예: X-Forwarded-For, X-Forwarded-Proto, Accept)만을 명시하는 화이트리스트 방식 설정을 강제하도록 아키텍처 규칙을 지정하십시오.
  • Verification command: grep -nF 'authRequestHeaders 로 인증 서버로 전달할' raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative.md
  • Verification result: 105: | D6 | authRequestHeaders 로 인증 서버로 전달할 헤더 필터링 (default empty = 모든 헤더 전달 — production 위험) | raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C4 | official-vendor-doc | default (empty) 가 Authorization 등 sensitive header 까지 인증 서버로 보내 production 위험 — 명시 화이트리스트 필요 |

L7-F04: In-Memory Token Storage와 제3자 쿠키 제한으로 인한 SPA Silent Renew 불가 현상

  • Source file: raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md
  • Source quote: token storage: in-memory (학습용, UserManager.events.addUserLoaded(...)로 closure 보관)
  • Source line: 61
  • Severity: High
  • Claim: XSS 공격 방어를 위해 SPA에서 in-memory 토큰 저장소를 기본 채택한 뒤, oidc-client-ts 라이브러리의 automaticSilentRenew: true 옵션만으로 토큰의 자동 갱신을 안정적으로 수행할 수 있을 것이라는 보안 아키텍처 가정.
  • Assumptions:
    1. SPA 애플리케이션 화면이 브라우저에서 리로드/새로고침될 시 토큰 정보 유실에 대응할 silent SSO 메커니즘이 원활히 동작할 것.
    2. Safari(ITP), Chrome(Privacy Sandbox) 등 최신 브라우저가 프레임(iframe)을 통한 제3자 쿠키(3rd-party cookie) 전송을 허용할 것.
  • Failure mode: in-memory 저장소를 쓰면 페이지 새로고침 시 토큰이 메모리에서 증발하므로 반드시 숨겨진 iframe을 띄워 OIDC authorization endpoint로 prompt=none 요청을 날려 세션을 복구해야 합니다(Silent SSO). 그러나 이 과정은 Keycloak 쿠키에 의존하므로, 브라우저의 제3자 쿠키 차단 정책이 활성화된 환경에서는 iframe 안에서의 Keycloak 쿠키 전송이 완전 거부되어 Silent Sign-In이 실패하게 되며, 사용자는 페이지를 새로고침할 때마다 계속 로그아웃되어 강제 재로그인 루프에 빠지게 됩니다.
  • Falsification condition: SPA와 Keycloak이 물리적으로 동일한 최상위 도메인(First-party context, 예: spa.example.com & kc.example.com)을 공유하여 제3자 쿠키가 아닌 당사자 쿠키로 간주되는 경우 장애가 발생하지 않습니다.
  • Recommendation: 학습 단계를 넘어 실제 운영 환경에서는 SPA 단독의 public client 흐름을 탈피하고, 백엔드를 인증 대리자로 삼는 BFF(Backend-For-Frontend) 패턴을 도입하여 프론트엔드 브라우저에는 오직 SameSite/HttpOnly 쿠키만 노출시키고 토큰은 백엔드 세션에 보관하도록 구조를 고도화하십시오.
  • Verification command: grep -nF 'token storage: in-memory' raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md
  • Verification result: 61: - token storage: in-memory (학습용, UserManager.events.addUserLoaded(...)로 closure 보관)

L7-F05: JSON Logstash Encoder 채택 시 PatternLayout 마스킹 필터 우회 취약성

  • Source file: raw/branch-notes/feature-log-management-contract.md
  • Source quote: Layer 1 (primary): Logback masking converter (PatternLayout 단계). 모든 ERROR/WARN 진입 시 token/password/auth header pattern을 ****로 치환.
  • Source line: 169
  • Severity: High
  • Claim: Logback 설정 파일의 PatternLayout 단계에서 정규식 기반의 Masking Converter를 적용해두면, 모든 보안 위협과 민감 자격 증명의 유출이 시스템 전체 로그에서 안정적으로 차단될 것이라는 운영 보안 가정.
  • Assumptions:
    1. 애플리케이션의 로그 출력 장치(Appender)가 항상 PatternLayout의 렌더링 형식을 타고 출력될 것.
    2. 구조화 로깅을 위한 JSON Encoder(예: logstash-logback-encoder)가 PatternLayout Converter 파이프라인을 온전히 준수할 것.
  • Failure mode: 프로덕션 환경의 성능 및 파싱 효율을 위해 logstash-logback-encoder와 같은 JSON 포맷 인코더를 적용하면, 이는 일반적인 Logback PatternLayout 문자열 변환 구조를 거치지 않고 객체를 직접 직렬화하여 JSON 스트림으로 내보냅니다. 이 경우 MDC나 Exception Stack Trace, 혹은 커스텀 구조화 필드에 담긴 민감 정보(패스워드, 토큰 등)는 PatternLayout 마스킹 정규식 필터를 전혀 타지 않고 원본 그대로 JSON에 바인딩되어 로그 서버로 노출되는 우회 취약점이 발생합니다.
  • Falsification condition: JSON Encoder 내부에 자체 Jackson Custom Masking Module을 등록하거나, Logback Filter 레벨에서 객체 단계의 마스킹 전처리를 수행하도록 조치한 경우 해당 실패가 방지됩니다.
  • Recommendation: PatternLayout Converter에만 마스킹 역할을 위임하지 말고, 구조화 JSON 인코더 설정 파일(logback-spring.xmlLoggingEventCompositeJsonEncoder)의 jsonGeneratorDecorator 또는 Jackson serializer 레벨에 PII 및 민감 자격 증명을 마스킹 처리해주는 커스텀 ValueMasker를 명시적으로 등록하십시오.
  • Verification command: grep -nF 'Logback masking converter' raw/branch-notes/feature-log-management-contract.md
  • Verification result: 169: - Layer 1 (primary): Logback masking converter (PatternLayout 단계). 모든 ERROR/WARN 진입 시 token/password/auth header pattern을 ****로 치환.

L7-F06: Custom SecurityFilterChain 선언 시 Actuator 전용 포트(9001)의 비인증 무단 노출 위험

  • Source file: raw/branch-notes/feature-management-actuator-security-contract.md
  • Source quote: management port default = 9001 (separate from app 8080). single-port는 platform ingress 보호 + 문서화 시만 허용.
  • Source line: 86
  • Severity: Critical
  • Claim: 애플리케이션 비즈니스 포트(8080)와 Actuator 관리 포트(9001)를 물리적으로 분리하는 것만으로 관리 서비스의 공격 노출 표면이 감소할 것이라는 보안적 아키텍처 가정.
  • Assumptions:
    1. 포트 분리 시 9001 포트의 라우팅이 호스트 내부망으로만 제약되어 안전하게 격리될 것.
    2. Spring Security가 활성화된 상황에서 custom SecurityFilterChain 빈을 정의할 때, 포트가 다른 Actuator endpoint에 대해서도 스프링이 안전하게 기본 보안 필터를 자동 바인딩해 줄 것.
  • Failure mode: Spring Boot 3에서 개발자가 메인 비즈니스 포트(8080)용으로 custom SecurityFilterChain 빈을 하나라도 수동 정의하면, 스프링 보안의 기본 auto-configuration(Actuator에 보안을 걸어주는 자동 구성)이 즉시 비활성화됩니다. 만약 Actuator 전용 포트인 9001용 FilterChain을 @Order 우선순위로 명시하여 별도 선언해주지 않는다면, 9001 포트로 들어오는 /actuator/env, /actuator/prometheus 등 모든 관리자 endpoint들이 비인증 무방비 상태로 열려 네트워크 전반에 정보 유출 표면이 노출되는 현상이 발생합니다.
  • Falsification condition: 9001 포트가 호스트 외부 IP 인터페이스에 바인딩되지 않도록 로컬 방화벽이나 인프라 시큐리티 그룹(Security Group) 레벨에서 인입 자체를 통제하는 환경에서는 노출 경로가 차단됩니다.
  • Recommendation: Actuator용 포트(9001) 보안을 위해 @Order(Ordered.HIGHEST_PRECEDENCE)를 적용한 전용 SecurityFilterChain 빈을 독립적으로 선언하여, 관리자 Endpoint 요청에 대해 Network ACL 또는 internal Admin Role 인증을 강제하도록 설정 표준을 아키텍처 규칙으로 명시하십시오.
  • Verification command: grep -nF 'management port default = 9001' raw/branch-notes/feature-management-actuator-security-contract.md
  • Verification result: 86: - 2026-05-22: management port default = 9001 (separate from app 8080). single-port는 platform ingress 보호 + 문서화 시만 허용.

L7-F07: Tenant ID의 Bucket Folding 제어로 인한 메트릭 식별성 유실 및 알람 무력화

  • Source file: raw/branch-notes/feature-metrics-alerting-contract.md
  • Source quote: tenant_id | 1000 (활성 시) — ULID 원본을 직접 사용하지 않음. metric label로는 (a) bounded mapping table id (tenant 등록 시 ascending integer 부여) 또는 (b) tenant cohort bucket(예: hash mod 100) 사용. 1001번째 tenant 등장 시 cardinality 정책: 새 tenant는 bucket으로 자동 fold. |
  • Source line: 172
  • Severity: Medium
  • Claim: Prometheus 메트릭의 Cardinality 폭주를 제어하기 위해 1,000개 이상의 다중 테넌트(Multi-tenant) 환경에서 1,001번째부터 테넌트 메트릭 라벨을 특정 Bucket으로 그룹화(Folding)하는 제어가 효율적일 것이라는 운영 가정.
  • Assumptions:
    1. 1001번째 이후의 테넌트들은 메트릭 레이블 상 개별 식별을 포기하고 통합 버킷으로 묶어도 운영 및 장애 파악에 무리가 없을 것.
    2. 특정 묶음 테넌트에서 대규모 시스템 장애가 발생했을 때, 해당 버킷 내의 타 테넌트 오염 없이 문제 원인을 격리 진단할 수 있을 것.
  • Failure mode: Cardinality 제어에는 기여하지만, 1001번째 이후의 테넌트들이 hash mod 100 버킷 등으로 압축 폴딩되면 장애 격리 능력을 유실하게 됩니다. 예컨대 특정 유료 대형 고객 테넌트(1050번째 등록)가 심각한 5xx 에러율 증가를 겪어도, 메트릭이 동일 버킷으로 폴딩되어 다른 테넌트들의 정상 트래픽에 묻혀 전체 버킷 평균치 에러율이 기준치 이하로 산출됨에 따라 알람 시스템이 작동하지 않는 무경보 장애 침묵 상황이 발생합니다.
  • Falsification condition: 테넌트 식별자 레벨의 장애 모니터링은 메트릭이 아닌 분산 트레이싱(Trace)이나 로깅(Log) 파이프라인에서만 실시간 전담하여 처리하도록 완전히 역할이 분담되어 있는 인프라 구조라면 위협성이 낮아집니다.
  • Recommendation: Prometheus 메트릭 Cardinality 억제를 위해 메트릭 태그 단독으로 테넌트 ID를 가공하는 폴딩 방식보다는, 최상위 50개 VIP 고객 전용 테넌트 리스트만 명시적 라벨로 유지하고, 나머지 일반 고객군은 NORMAL_TENANTS 통합 버킷으로 묶는 이원화된 동적 Allowlist 필터링 규칙을 정의하여 비즈니스 영향도가 큰 고객에 대한 얼럿 정확도를 유지하십시오.
  • Verification command: grep -nF '1001번째 tenant 등장 시 cardinality' raw/branch-notes/feature-metrics-alerting-contract.md
  • Verification result: 172: | tenant_id | 1000 (활성 시) — ULID 원본을 직접 사용하지 않음. metric label로는 (a) bounded mapping table id (tenant 등록 시 ascending integer 부여) 또는 (b) tenant cohort bucket(예: hash mod 100) 사용. 1001번째 tenant 등장 시 cardinality 정책: 새 tenant는 bucket으로 자동 fold. |

L7-F08: Clustered 다중 인스턴스 환경에서 Flyway Schema Lock 타임아웃으로 인한 컨테이너 Startup Fail

  • Source file: raw/branch-notes/feature-migration-startup-contract.md
  • Source quote: multi-instance에서는 app startup runner를 그대로 확장하지 않고 platform one-shot job 또는 migration lock 검증이 필요.
  • Source line: 86
  • Severity: High
  • Claim: 다중 인스턴스(Replicas > 1) 환경에서 각 애플리케이션의 Flyway Startup Runner가 자체 데이터베이스 락 테이블 검증을 수행하므로, 별도 분리 작업 없이 단순히 Replicas를 증설하여 배포하는 것만으로 배포 안정성이 유지될 것이라는 설계 가정.
  • Assumptions:
    1. 배포 중 신규 Pod들이 동시에 데이터베이스 마이그레이션 변경 테이블(flyway_schema_history)에 락(Lock)을 획득하려고 경쟁하는 시간이 데이터베이스 락 획득 타임아웃보다 충분히 짧을 것.
    2. 락 경합으로 인한 startup 일시 지연이 발생하더라도 애플리케이션 프로세스가 에러로 종료되지 않고 안정적으로 대기할 것.
  • Failure mode: 쿠버네티스 등 롤링 배포 시점에 다수의 API 컨테이너 Replicas가 거의 동시에 부팅되면서 각각 Flyway migration을 DB 연결 후 실행하려 합니다. Flyway의 schema history table lock 획득을 위해 경합하는 과정에서 첫 인스턴스가 락을 잡한 상태로 대형 마이그레이션 스크립트를 수행하면, 나머지 대기 중이던 컨테이너들은 기본 락 타임아웃 임계치를 넘겨 결국 LockException을 던지며 시스템 시작 시점 종료 코드 70을 반환하고 대거 비정상 종료(Startup Failure)되어 롤링 배포 전체가 롤백되거나 지연됩니다.
  • Falsification condition: 배포 파이프라인에서 컨테이너 기동 전 데이터베이스 스키마 마이그레이션이 단일 직렬 작업(One-shot Job)으로 확실하게 격리되어 마친 뒤 컨테이너들이 기동되는 단방향 흐름이 보장된다면 경합 문제가 방지됩니다.
  • Recommendation: 프로덕션 배포 스크립트 작성 시 애플리케이션 내의 spring.flyway.enabled를 기본 false로 끄고, 마이그레이션을 전담하는 일회성 독립 파이프라인(Kubernetes Job 등)을 먼저 기동하여 완료된 것을 헬스체크 신호로 삼아 애플리케이션 인스턴스들을 부팅하도록 아키텍처 배포 정책을 표준화하십시오.
  • Verification command: grep -nF 'multi-instance에서는 app startup' raw/branch-notes/feature-migration-startup-contract.md
  • Verification result: 86: - 2026-05-22: multi-instance에서는 app startup runner를 그대로 확장하지 않고 platform one-shot job 또는 migration lock 검증이 필요.

L7-F09: Reactive WebFlux 스택 전용 MDC Context 전파(Propagation) 누락에 의한 트레이스 ID 유실

  • Source file: raw/branch-notes/feature-operational-error-observability-foundation.md
  • Source quote: MDC key snake_case (request_id) 가 Spring MVC RequestContextHolder 와 Reactor Context 양쪽에서 일관 propagation
  • Source line: 411
  • Severity: High
  • Claim: MDC 키를 snake_case 형식으로 지정하고 ThreadLocal 기반 컨텍스트 전파 방식을 정의하는 것만으로 비동기 및 리액티브 스택 전체의 로깅 파이프라인에서 트레이스 ID 전파가 잘 유지될 것이라는 일반성 가정.
  • Assumptions:
    1. 애플리케이션 내부에서 스레드 풀 전환(Thread Context Switch)이 일어나더라도 ThreadLocal 기반의 MDC 데이터가 대상 비동기 스레드로 잘 상속될 것.
    2. Spring WebFlux 등의 Reactive Stream 파이프라인 내 모든 연산자(Operator) 경계에서 MDC 전파가 누수 없이 작동할 것.
  • Failure mode: Spring WebFlux(Project Reactor) 환경에서는 요청 처리 흐름이 특정 고정 스레드에 묶이지 않고 여러 이벤트 루프 스레드를 넘나듭니다. ThreadLocal을 기본 저장소로 삼는 MDC는 리액티브 스트림 내부에서 컨텍스트 경계를 넘을 때(예: publishOn, subscribeOn 등 비동기 바운더리) 자동으로 전파되지 않고 깨끗이 삭제됩니다. 이에 따라, 로그 백엔드 상에서 에러 발생 지점의 로그들을 단일 request_idtrace_id로 연관 지으려 해도 트레이스 연결 관계가 중간에 끊어져버려 비동기 장애에 대한 추적이 불가능해지는 현상이 발생합니다.
  • Falsification condition: 동기식 톰캣(Tomcat) 기반의 Spring MVC 표준 스택 및 단일 동기 스레드 바운더리 내에서만 작동하는 동기 처리 비즈니스 로직으로 ca-tmpl의 스펙을 제약하는 경우에는 해당 문제가 발생하지 않습니다.
  • Recommendation: 리액티브 동작 모델에서의 로깅 정합성을 위해, Micrometer Context Propagation 라이브러리를 프로젝트 기본 모듈에 편입시키고 Reactor Context의 Key-Value 구조를 MDC 스레드 로컬 영역에 매칭/해제시켜주는 Custom Reactor Hook이나 MdcContextLifter를 구현하여 비동기 경계 간 MDC 전파를 보장하십시오.
  • Verification command: grep -nF 'RequestContextHolder 와 Reactor Context' raw/branch-notes/feature-operational-error-observability-foundation.md
  • Verification result: 411: | MDC key snake_case (request_id) 가 Spring MVC RequestContextHolder 와 Reactor Context 양쪽에서 일관 propagation | foundation 결정 — 실제 reactive stack 에서 MDC 전파 확인 필요 | reactive integration test + @Async |