Files
llm-wiki/docs/superpowers/specs/2026-07-18-keycloak-branch-note-consistency/lanes/lane-02-keycloak-sorted-11-20.md
T

34 KiB
Raw Blame History

Lane Inventory

Path Status Evidence Lines Extracted Facts
raw/branch-notes/feature-keycloak-first-broker-login-flow.md READ_FULL 1261 D1/D2 Confirm Link 소유증명, D4 silent auto-link 차단, hard-reject SPI 별도 트랙
raw/branch-notes/feature-keycloak-google-claim-attribute-mapping.md READ_FULL 1238 attribute-mapping owner, D3 IdP default IMPORT, D5 role mapping 위임
raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md READ_FULL 1261 exact redirect URI, basic-scope 예외, JavaScript origins 비움
raw/branch-notes/feature-keycloak-header-spoofing-defense.md READ_FULL 1255 K8s NetworkPolicy·EC2 SG·shared secret·trusted proxy 방어 경계
raw/branch-notes/feature-keycloak-https-termination-caddy-nginx.md READ_FULL 1284 TLS 종단 대안과 명시적 HSTS 구성
raw/branch-notes/feature-keycloak-idp-brokering-google-client.md READ_FULL 1251 Google IdP client, trustEmail=false, callback·origin 위임
raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md READ_FULL 1148 role/RBAC owner, D1 FORCE, D2 email verification 강제 주장
raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md READ_FULL 1512 P2B composition hub, D8 zero-change brokering, D10 SMTP 사실, foreign-decision 상세 재진술
raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md READ_FULL 1416 P2A SPA Direct 정의, D2 refresh-token 저장, D4 brokering 비교
raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch.md READ_FULL 1287 issuer/JWKS 분리 대안, 미승인 F, 외부 owner 충돌 명시

File 1 — feature-keycloak-first-broker-login-flow.md

  • Severity: High
  • Failure modes: STALE_SUMMARY, UNSUPPORTED_DECISION
  • Source files: first-broker-login-flow, idp-brokering-google-client, internal-spa-direct-google-federation
  • Original goal:
    • 인용:
      core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립
      
    • 위치: raw/branch-notes/feature-keycloak-first-broker-login-flow.md:135
  • Current state:
    • 인용:
      이는 `trustEmail=false`+email 인증 상태에서도 Confirm Link 흐름이 우회 가능했던 실제(패치완료) 취약점
      
    • 위치: raw/branch-notes/feature-keycloak-idp-brokering-google-client.md:214
    • 인용:
      `CVE-2026-9087` 은 **raw 에 아카이브된 출처가 없다**
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:338
  • Claim: 문서 상태에 관한 FACT는 세 노트가 서로 다른 보장 강도를 제공한다는 것이다. 실제 취약점·영향 버전은 아직 INFERENCE/UNVERIFIED다.
  • Assumptions: F의 취약점 보고가 정확하고 배포 버전이 영향 범위일 때만 owner의 무조건부 보장이 실패한다.
  • Concrete failure: 구현자가 line 135만 소비하면 패치 상태를 확인하지 않고 Confirm Link를 충분한 차단으로 취급할 수 있다.
  • Falsification: 공식 Keycloak advisory가 해당 흐름에 영향이 없다고 밝히거나, 배포 버전이 영향 범위 밖임을 증명하면 보안 충돌은 사라진다.
  • Required action / synthesis recommendation: 공식 advisory를 raw로 보존한 뒤 owner D4에 버전 조건을 추가한다. 그 전에는 F의 “실제 취약점” 표현을 needs-confirmation으로 낮추고, owner에는 미검증 version gate만 둔다.
  • Why: 보안 owner의 보장은 소비자가 한 줄만 읽어도 유효 조건을 알 수 있어야 한다.
  • Alternatives: ① 패치 버전 allowlist 게이트, ② 취약 버전 배포 차단, ③ 별도 custom SPI 방어, ④ Google brokering 비활성화.
  • Counterargument: 현재 지원 버전만 사용하면 Confirm Link 보장은 유효할 수 있다. 그렇더라도 owner 문서에 적용 버전 조건을 적으면 이 반론과 충돌하지 않는다.
  • Verification:
    grep -nFo -- 'core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립' raw/branch-notes/feature-keycloak-first-broker-login-flow.md
    # Observed: 135:core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립
    
    grep -nFo -- '이는 `trustEmail=false`+email 인증 상태에서도 Confirm Link 흐름이 우회 가능했던 실제(패치완료) 취약점' raw/branch-notes/feature-keycloak-idp-brokering-google-client.md
    # Observed: 214:이는 `trustEmail=false`+email 인증 상태에서도 Confirm Link 흐름이 우회 가능했던 실제(패치완료) 취약점
    
    grep -nFo -- '`CVE-2026-9087` 은 **raw 에 아카이브된 출처가 없다**' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 338:`CVE-2026-9087` 은 **raw 에 아카이브된 출처가 없다**
    
  • Severity: High
  • Failure modes: CONTRADICTION, RESTATED_FOREIGN_DECISION
  • Source files: first-broker-login-flow, idp-mappers-claim-to-role, internal-spa-direct-google-federation
  • Original goal:
    • 인용:
      `email_verified=false` 계정의 **silent auto-link 차단**
      
    • 위치: raw/branch-notes/feature-keycloak-first-broker-login-flow.md:135
    • 인용:
      `email_verified=false` 를 flow 진입 즉시 *hard-reject* 하려면 커스텀 SPI authenticator 필요 → 별도 트랙
      
    • 위치: raw/branch-notes/feature-keycloak-first-broker-login-flow.md:135
  • Current state:
    • 인용:
      `email_verified=true` 강제 (Attribute Importer + First Broker Login Flow custom step)
      
    • 위치: raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md:120
    • 인용:
      Google `email_verified=false` 계정 | 링크/생성 거부
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:387
  • Claim: owner D4는 소유증명 없는 auto-link만 차단한다. G와 H는 별도 SPI가 구현된 것처럼 전체 링크·생성을 거부한다고 요약한다.
  • Assumptions: slice 안에는 hard-reject custom authenticator의 별도 owner나 구현 증거가 없다.
  • Concrete failure: email_verified=false 사용자가 실제로는 Confirm Link 또는 신규 계정 흐름에 진입하는데, 테스트가 즉시 거부를 기대해 보안 정책과 구현이 어긋난다.
  • Falsification: 별도 owner가 custom authenticator 구현과 E2E 거부 결과를 제시하면 G/H의 정책이 성립한다.
  • Required action / synthesis recommendation: G/H를 owner D4의 “silent-link prevention only” 포인터로 축소한다. hard-reject가 필요하면 SPI branch를 단일 owner로 만들고 그 결정이 확정된 뒤 세 문서를 갱신한다.
  • Why: 계정 연결 방어와 모든 계정 생성 거부는 서로 다른 정책·구현 계약이다.
  • Alternatives: ① Confirm Link만 유지, ② custom SPI로 즉시 거부, ③ 통제된 enterprise IdP만 허용, ④ Google federation 자체를 사용하지 않음.
  • Counterargument: Google이 일반적으로 verified email을 제공하므로 차이가 드물 수 있다. 발생 빈도와 정책 보장은 별개이며, 문서는 미검증 claim의 실제 동작을 정의해야 한다.
  • Verification:
    grep -nFo -- '`email_verified=false` 계정의 **silent auto-link 차단**' raw/branch-notes/feature-keycloak-first-broker-login-flow.md
    # Observed: 135:`email_verified=false` 계정의 **silent auto-link 차단**
    
    grep -nFo -- '`email_verified=false` 를 flow 진입 즉시 *hard-reject* 하려면 커스텀 SPI authenticator 필요 → 별도 트랙' raw/branch-notes/feature-keycloak-first-broker-login-flow.md
    # Observed: 135:`email_verified=false` 를 flow 진입 즉시 *hard-reject* 하려면 커스텀 SPI authenticator 필요 → 별도 트랙
    
    grep -nFo -- '`email_verified=true` 강제 (Attribute Importer + First Broker Login Flow custom step)' raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md
    # Observed: 120:`email_verified=true` 강제 (Attribute Importer + First Broker Login Flow custom step)
    
    grep -nFo -- 'Google `email_verified=false` 계정 | 링크/생성 거부' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 387:Google `email_verified=false` 계정 | 링크/생성 거부
    

File 2 — feature-keycloak-google-claim-attribute-mapping.md

L2-F03: IMPORT owner와 FORCE 소비자의 적용 범위가 불명확함

  • Severity: Medium
  • Failure modes: AMBIGUOUS_OWNERSHIP, 조건부 CONTRADICTION
  • Source files: google-claim-attribute-mapping, idp-mappers-claim-to-role
  • Original goal:
    • 인용:
      그쪽이 attribute-mapping **owner**
      
    • 위치: raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md:19
  • Current state:
    • 인용:
      | D1 | Sync Mode = `FORCE`
      
    • 위치: raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md:119
    • 인용:
      | D3 | Sync Mode = IMPORT (first login 시점 매핑만)
      
    • 위치: raw/branch-notes/feature-keycloak-google-claim-attribute-mapping.md:130
    • 인용:
      IdP-level `Sync Mode` = 모든 mapper default
      
    • 위치: raw/branch-notes/feature-keycloak-google-claim-attribute-mapping.md:130
  • Claim: B가 IdP-level default를 IMPORT로 소유하지만, G의 D1은 FORCE가 global인지 role mapper override인지 밝히지 않는다.
  • Assumptions: G D1이 IdP-level 설정을 가리키면 두 값은 공존할 수 없다. mapper-level override이고 Keycloak이 해당 override를 지원한다면 공존 가능하다.
  • Concrete failure: 운영자가 동일 Admin UI 필드를 두 지침대로 번갈아 설정하거나, 모든 attribute를 로그인마다 덮어써 사용자 편집값을 잃을 수 있다.
  • Falsification: G D1을 특정 role mapper의 override로 명시하고 공식 근거·Admin UI 실측으로 override 우선순위를 확인하면 충돌이 해소된다.
  • Required action / synthesis recommendation: B D3를 IdP default owner로 유지하고, G D1은 role mapper 전용 override로 증명·범위화하거나 제거한다.
  • Why: fresh role과 보존해야 할 profile attribute는 서로 다른 갱신 정책을 가질 수 있으나 설정 계층을 분명히 해야 한다.
  • Alternatives: ① global IMPORT + role mapper FORCE, ② 전부 IMPORT, ③ 전부 FORCE, ④ role 계산을 별도 authorization 계층으로 이동.
  • Counterargument: role freshness에는 FORCE가 합리적이다. 이 finding은 FORCE 자체가 아니라 어느 설정 계층에 적용하는지 불명확한 점을 지적한다.
  • Verification:
    grep -nFo -- '그쪽이 attribute-mapping **owner**' raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md
    # Observed: 19:그쪽이 attribute-mapping **owner**
    
    grep -nFo -- '| D1 | Sync Mode = `FORCE`' raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md
    # Observed: 119:| D1 | Sync Mode = `FORCE`
    
    grep -nFo -- '| D3 | Sync Mode = IMPORT (first login 시점 매핑만)' raw/branch-notes/feature-keycloak-google-claim-attribute-mapping.md
    # Observed: 130:| D3 | Sync Mode = IMPORT (first login 시점 매핑만)
    
    grep -nFo -- 'IdP-level `Sync Mode` = 모든 mapper default' raw/branch-notes/feature-keycloak-google-claim-attribute-mapping.md
    # Observed: 130:| D3 | Sync Mode = IMPORT (first login 시점 매핑만)
    # Observed: 173:IdP-level `Sync Mode` = 모든 mapper default
    

File 3 — feature-keycloak-google-redirect-uri-policy.md

L2-F04: basic-scope 예외를 옛 test-user 한도 설명이 덮음

  • Severity: Low
  • Failure mode: STALE_SUMMARY
  • Source file: google-redirect-uri-policy
  • Original goal:
    • 인용:
      이 basic identity scope 조합은 test-user allowlist 등록·100명 상한·7일 만료·unverified 경고가 모두 면제됨
      
    • 위치: raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md:143
  • Current state:
    • 인용:
      학습용은 `openid email profile`만 사용 → unverified로 100명 test users까지 동작
      
    • 위치: raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md:92
  • Claim: D5의 정정 이후에도 TODO가 이전 한도 모델을 유지한다.
  • Assumptions: 요청 scope가 정말 openid email profile에 한정된다.
  • Concrete failure: 불필요한 test-user 등록·인원 관리 절차를 구현 계획에 넣고, 서로 다른 Google quota를 혼동한다.
  • Falsification: sensitive/restricted scope가 실제로 추가되면 TODO의 한도 설명이 별도 시나리오에서 다시 필요하다.
  • Required action / synthesis recommendation: line 92를 D5 포인터로 바꾸거나 삭제하고, sensitive-scope 시나리오는 별도 조건문으로 분리한다.
  • Why: 한 파일 안의 TODO가 authoritative D-row보다 오래된 운영 지침을 제공한다.
  • Alternatives: ① TODO 삭제 후 D5 포인터, ② 최신 예외 설명으로 갱신, ③ sensitive-scope 실험용 별도 TODO로 범위화.
  • Counterargument: Testing 상태의 다른 quota를 기억시키려는 문장일 수 있다. 그렇다면 basic identity scope와 다른 quota임을 명시해야 한다.
  • Verification:
    grep -nFo -- '이 basic identity scope 조합은 test-user allowlist 등록·100명 상한·7일 만료·unverified 경고가 모두 면제됨' raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md
    # Observed: 143:이 basic identity scope 조합은 test-user allowlist 등록·100명 상한·7일 만료·unverified 경고가 모두 면제됨
    
    grep -nFo -- '학습용은 `openid email profile`만 사용 → unverified로 100명 test users까지 동작' raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md
    # Observed: 92:학습용은 `openid email profile`만 사용 → unverified로 100명 test users까지 동작
    

L2-F05: JavaScript origins가 owner와 consumer에서 다름

  • Severity: Medium
  • Failure modes: STALE_SUMMARY, RESTATED_FOREIGN_DECISION
  • Source files: google-redirect-uri-policy, idp-brokering-google-client
  • Original goal:
    • 인용:
      | Authorized JavaScript origins | (비움) |
      
    • 위치: raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md:167
  • Current state:
    • 인용:
      Authorized JavaScript origins: `https://<kc-host>`
      
    • 위치: raw/branch-notes/feature-keycloak-idp-brokering-google-client.md:81
  • Claim: redirect-policy owner는 server-side flow 때문에 origins를 비우지만, Google-client TODO는 외부 Keycloak host를 등록하라고 한다.
  • Assumptions: Google OAuth 교환은 Keycloak 서버가 수행하며 브라우저 Google SDK를 직접 사용하지 않는다.
  • Concrete failure: 불필요한 browser origin을 허용하고, 동일 client를 재현하는 사람마다 다른 Console 설정을 만든다.
  • Falsification: Google Console 실측에서 해당 client type이 origin을 요구하거나 실제 browser-side Google SDK 흐름이 추가되면 host 등록이 필요하다.
  • Required action / synthesis recommendation: F의 origin 값을 삭제하고 C D6 포인터로 교체한다. 브라우저 흐름이 생기면 별도 client와 owner를 둔다.
  • Why: callback URI와 JavaScript origin은 다른 정책 필드이며, consumer가 owner 값을 재정의하면 drift가 생긴다.
  • Alternatives: ① 비워 둠, ② browser flow 도입 시 최소 origin만 등록, ③ browser용 Google client 분리.
  • Counterargument: host 하나를 미리 등록해도 당장 실패하지 않을 수 있다. 그러나 사용하지 않는 권한 표면과 재현 차이는 남는다.
  • Verification:
    grep -nFo -- '| Authorized JavaScript origins | (비움) |' raw/branch-notes/feature-keycloak-google-redirect-uri-policy.md
    # Observed: 167:| Authorized JavaScript origins | (비움) |
    
    grep -nFo -- 'Authorized JavaScript origins: `https://<kc-host>`' raw/branch-notes/feature-keycloak-idp-brokering-google-client.md
    # Observed: 81:Authorized JavaScript origins: `https://<kc-host>`
    

File 4 — feature-keycloak-header-spoofing-defense.md

0 independent findings. D3(K8s)과 D4(EC2)는 환경별 대안이며 동시에 적용해야 하는 상충 결정이 아니다. D5는 두 환경의 보조 계층, D6는 Keycloak proxy 신뢰 경계로 분리돼 있다. 다만 부모·proxy owner가 slice 밖이므로 lines 202206은 NEEDS_CROSS_LANE_AUDIT로 이관한다.

File 5 — feature-keycloak-https-termination-caddy-nginx.md

L2-F06: Caddy HSTS 기본값 설명이 authoritative 구성과 상충함

  • Severity: Medium
  • Failure mode: STALE_SUMMARY
  • Source file: https-termination-caddy-nginx
  • Original goal:
    • 인용:
      **명시 필요** (default 로 단정 금지)
      
    • 위치: raw/branch-notes/feature-keycloak-https-termination-caddy-nginx.md:173
  • Current state:
    • 인용:
      Caddy는 디폴트로 HTTPS redirect + HSTS
      
    • 위치: raw/branch-notes/feature-keycloak-https-termination-caddy-nginx.md:43
  • Claim: 구현 표는 HSTS를 명시하라고 정정했지만 초기 설계 답변은 여전히 자동 적용이라고 말한다.
  • Assumptions: 별도 global Caddy 설정이나 upstream edge가 HSTS를 주입하지 않는다.
  • Concrete failure: line 43을 따라 구성하면 HTTPS redirect는 동작해도 HSTS 헤더가 빠질 수 있다.
  • Falsification: 대상 Caddy 버전의 공식 문서나 응답 헤더 실측이 자동 HSTS를 입증하면 line 43이 맞다.
  • Required action / synthesis recommendation: line 43을 “automatic HTTPS redirect, HSTS explicit”으로 정정하고 D2/D5에만 값을 소유시킨다.
  • Why: TLS 자동화와 HSTS 정책은 서로 다른 기능이다.
  • Alternatives: ① Caddy header 명시, ② nginx에서 명시, ③ Cloudflare edge에서 단일 owner로 주입, ④ ephemeral dev에서는 의도적으로 생략하고 범위를 표시.
  • Counterargument: 학습 환경에서는 HSTS가 필요하지 않을 수 있다. 그렇다면 dev-only 생략 결정으로 적어야지 default 기능으로 설명하면 안 된다.
  • Verification:
    grep -nFo -- 'Caddy는 디폴트로 HTTPS redirect + HSTS' raw/branch-notes/feature-keycloak-https-termination-caddy-nginx.md
    # Observed: 43:Caddy는 디폴트로 HTTPS redirect + HSTS
    
    grep -nFo -- '**명시 필요** (default 로 단정 금지)' raw/branch-notes/feature-keycloak-https-termination-caddy-nginx.md
    # Observed: 173:**명시 필요** (default 로 단정 금지)
    

File 6 — feature-keycloak-idp-brokering-google-client.md

0 additional independent findings. 이 파일은 L2-F01의 미아카이브 CVE 주장과 L2-F05의 stale JavaScript-origin TODO에 직접 참여한다. 그 외 D6의 trustEmail=false 값 선택과 default 미확정 구분은 내부적으로 일관된다. 부모가 정하는 <kc-host>는 slice 밖이므로 line 192를 교차 lane 후보로 남긴다.

File 7 — feature-keycloak-idp-mappers-claim-to-role.md

0 additional independent findings. 이 파일의 실제 상충은 L2-F02의 hard-reject 주장과 L2-F03의 FORCE 적용 범위로 모두 수거했다. role/RBAC만 소유한다는 line 19와 attribute-level D1/D2가 함께 남아 있는 것이 두 finding의 공통 원인이다.

File 8 — feature-keycloak-internal-spa-direct-google-federation.md

L2-F07: Reference-Only를 선언하면서 foreign decision 상세를 의도적으로 유지함

  • Severity: Medium
  • Failure mode: RESTATED_FOREIGN_DECISION
  • Source file: internal-spa-direct-google-federation
  • Original goal:
    • 인용:
      본 노트는 `rules/consistency-contract.md` 의 **Reference-Only** 규약에 따라 포인터 + 1줄 요약으로만 인용한다
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:22
  • Current state:
    • 인용:
      **유지 + 위임 고지** — 학습용 흐름 이해에 필요한 개요이므로 삭제하지 않고
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:443
  • Claim: 위임 고지는 Reference-Only의 “포인터 + 1줄” 제한을 대체하지 않는다. 파일 스스로 detailed restatement를 식별하면서 유지하기로 했다.
  • Assumptions: 현재 consistency-contract의 Single-Owner/Reference-Only 규칙이 그대로 적용된다.
  • Concrete failure: attribute mapping 또는 trust-chain owner가 바뀔 때 H의 상세 표가 같은 변경을 수동으로 따라가지 못해 stale copy가 된다.
  • Falsification: 규칙이 synchronized derived view를 허용하도록 바뀌고, owner 변경 때 자동 검증되는 생성물이 되면 상세 유지가 가능하다.
  • Required action / synthesis recommendation: branch-note에서는 owner 포인터와 한 줄 요약만 남긴다. 학습용 상세 설명은 canonical concept나 owner에서 생성되는 view로 이동한다.
  • Why: “owner가 우선”이라는 주석은 독자가 stale 상세를 실행하는 것을 막지 못한다.
  • Alternatives: ① 한 줄 pointer-only, ② canonical explainer로 이동, ③ owner 기반 자동 생성 view, ④ H가 새 owner가 되고 기존 owner를 pointer로 전환.
  • Counterargument: composition hub에는 end-to-end 이해를 위한 상세가 필요하다. 그 요구는 canonical explainer나 생성 view로 충족할 수 있다.
  • Verification:
    grep -nFo -- '본 노트는 `rules/consistency-contract.md` 의 **Reference-Only** 규약에 따라 포인터 + 1줄 요약으로만 인용한다' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 22:본 노트는 `rules/consistency-contract.md` 의 **Reference-Only** 규약에 따라 포인터 + 1줄 요약으로만 인용한다
    
    grep -nFo -- '**유지 + 위임 고지** — 학습용 흐름 이해에 필요한 개요이므로 삭제하지 않고' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 443:**유지 + 위임 고지** — 학습용 흐름 이해에 필요한 개요이므로 삭제하지 않고
    

L2-F08: brokering zero-change payload에 두 owner가 존재함

  • Severity: Medium
  • Failure mode: DUAL_OWNERSHIP
  • Source files: internal-spa-direct-google-federation, internal-spa-direct-no-google
  • Original goal:
    • 인용:
      P2B 의 federation 은 **Keycloak 레이어에서만** broker
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:324
  • Current state:
    • 인용:
      brokering 은 SPA/Backend **코드 0줄**로 얹히는 cross-cutting
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md:282
    • 인용:
      **이중 주장 (`/sync` 수거 대상)**
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md:402
  • Claim: H D8과 I D4가 동일한 zero-change 결론을 각각 결정 payload로 보유하며 H도 이를 이중 주장으로 인정한다.
  • Assumptions: “Keycloak-only broker”와 “SPA/backend code zero”가 같은 구조적 보장의 두 표현이다.
  • Concrete failure: kc_idp_hint, 로그인 버튼 노출 기본값, SPA 변경 조건이 진화할 때 두 D-row가 다른 경계를 가질 수 있다.
  • Falsification: I D4를 범위 선택과 H D8 pointer만 남기도록 축소하면 소유권이 분리된다.
  • Required action / synthesis recommendation: H D8을 composition owner로 지정하고 I D4를 포인터로 전환한다. 별도 federation-spa-zero-change owner를 택하려면 먼저 그 unseen branch와 교차 감사를 수행한다.
  • Why: 아키텍처 불변성은 여러 구현 결정의 전제가 되므로 한 곳에서 조건과 예외를 소유해야 한다.
  • Alternatives: ① H D8 owner, ② I D4 owner, ③ zero-change 전용 child owner, ④ project hub의 고정 결정으로 승격.
  • Counterargument: baseline과 extension 양쪽에 같은 설명이 있어야 읽기 쉽다. pointer와 한 줄 요약이면 가독성을 유지하면서 owner를 하나로 둘 수 있다.
  • Verification:
    grep -nFo -- 'P2B 의 federation 은 **Keycloak 레이어에서만** broker' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 324:P2B 의 federation 은 **Keycloak 레이어에서만** broker
    
    grep -nFo -- 'brokering 은 SPA/Backend **코드 0줄**로 얹히는 cross-cutting' raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md
    # Observed: 282:brokering 은 SPA/Backend **코드 0줄**로 얹히는 cross-cutting
    
    grep -nFo -- '**이중 주장 (`/sync` 수거 대상)**' raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation.md
    # Observed: 402:**이중 주장 (`/sync` 수거 대상)**
    

L2-F01과 L2-F02도 이 파일의 version gate 및 email_verified edge table에 직접 적용된다. D10의 SMTP 소유권은 실제 docker-compose owner가 slice 밖이라 finding으로 확정하지 않고 교차 lane 후보로 남겼다.

File 9 — feature-keycloak-internal-spa-direct-no-google.md

  • Severity: High
  • Failure modes: INCOMPATIBLE_ASSUMPTIONS, RESTATED_FOREIGN_DECISION
  • Source file: internal-spa-direct-no-google
  • Original goal:
    • 인용:
      **Edge proxy 없음.**
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md:17
  • Current state:
    • 인용:
      **secure httpOnly cookie** (BFF 변형)
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md:188
    • 인용:
      | D2 | refresh token 저장 위치 = secure httpOnly cookie 권장
      
    • 위치: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md:280
  • Claim: INFERENCE — HttpOnly refresh cookie를 발급·회전·소비하려면 token-mediating server가 필요하다. 정의된 backend는 Resource Server일 뿐이고 edge도 없으므로 D2 권장은 현재 패턴 안에서 실행할 주체가 없다.
  • Assumptions: 브라우저 SPA는 HttpOnly cookie 값을 읽을 수 없고, Keycloak이 이 프로젝트 origin용 refresh cookie를 대신 관리하도록 설계되지 않았다.
  • Concrete failure: 구현자가 Resource Server에 token exchange·cookie issuance·CSRF 방어를 뒤늦게 추가하면서 P2A를 사실상 BFF/AP3로 바꾸지만 문서·threat model은 SPA Direct로 남는다.
  • Falsification: 별도 mediator 없이 Keycloak/browser adapter가 usable HttpOnly refresh cookie를 제공한다는 공식 근거와 E2E 결과가 있거나, 이미 mediator가 아키텍처에 포함돼 있음을 증명하면 성립하지 않는다.
  • Required action / synthesis recommendation: P2A 기본은 memory-only로 두거나, HttpOnly 선택 시 명시적으로 token-mediating/BFF 패턴으로 전환하고 CSRF·rotation owner를 함께 연결한다. unseen token-storage owner도 교차 감사해야 한다.
  • Why: storage 선택이 토큰 보유 주체와 인증 아키텍처를 바꾸고 있다.
  • Alternatives: ① memory-only, ② Web Worker 격리, ③ 최소 token-mediator 추가, ④ full BFF, ⑤ browser storage 사용 후 XSS 위험 수용.
  • Counterargument: reload 후 세션 유지에는 HttpOnly cookie UX가 낫다. 그 장점은 mediator 도입 비용과 CSRF 계약을 명시적으로 수용할 때만 비교할 수 있다.
  • Verification:
    grep -nFo -- '**Edge proxy 없음.**' raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md
    # Observed: 17:**Edge proxy 없음.**
    
    grep -nFo -- '**secure httpOnly cookie** (BFF 변형)' raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md
    # Observed: 188:**secure httpOnly cookie** (BFF 변형)
    
    grep -nFo -- '| D2 | refresh token 저장 위치 = secure httpOnly cookie 권장' raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md
    # Observed: 280:| D2 | refresh token 저장 위치 = secure httpOnly cookie 권장
    

L2-F08의 zero-change 이중 소유도 이 파일 D4에 적용된다.

File 10 — feature-keycloak-iss-claim-hostname-mismatch.md

0 slice-internal findings. D6의 jwk-set-uri 대안은 부모와 audience-validator owner가 승인하기 전까지 권고안으로만 유지되고, 현재 기본을 C로 명시해 파일 내부에서 두 구성을 동시에 채택하지 않는다. KC-HOST-C6는 미아카이브 claim 후보라고 lines 142·230·247에 명시돼 Supporting Claim으로 위장하지 않는다. 외부 owner 충돌은 lines 204209의 교차 lane 후보로 분리한다.

Synthesis

이 slice에는 High 3건, Medium 5건, Low 1건의 정합 gap이 있다. 가장 직접적인 보안 오해는 email_verified=false를 silent-link 차단과 hard-reject로 혼용한 L2-F02다. L2-F01의 버전 위험은 파급은 크지만 공식 advisory가 raw에 없어 실재·영향 범위를 아직 FACT로 취급할 수 없다. IMPORT/FORCE, JavaScript origins, HSTS는 owner 값보다 오래된 소비자·초기 설명이 남은 drift다. P2B는 Reference-Only를 선언하면서 상세 foreign decisions를 유지하고, zero-change 결론도 P2A와 공동 소유한다. P2A의 HttpOnly-cookie 권장은 SPA Direct와 BFF의 경계를 흐리므로 구현 전 owner 결정이 필요하다. Header-spoofing과 issuer-mismatch 파일은 slice 내부에서는 대안 조건과 미승인 상태를 구분하고 있다.

Inferences

  1. CVE 보고가 정확할 때만 owner의 무조건부 Confirm Link 보장이 영향 버전에서 실패한다 — Based on: feature-keycloak-idp-brokering-google-client.md:214, feature-keycloak-internal-spa-direct-google-federation.md:338.
  2. G D1이 mapper-level override라면 IMPORT/FORCE는 공존할 수 있다 — Based on: feature-keycloak-google-claim-attribute-mapping.md:130, feature-keycloak-idp-mappers-claim-to-role.md:119.
  3. HttpOnly refresh cookie에는 token-mediating server가 필요하다 — Based on: feature-keycloak-internal-spa-direct-no-google.md:17,188,280. 실제 adapter 동작으로 반증 가능하다.

Out of Scope

  • Slice 밖 parent/project/owner branch의 본문 의미 감사
  • Keycloak·Google Console·Caddy 실제 구동
  • CVE 공식 advisory 외부 fetch 및 raw 보존
  • branch-note 수정 또는 canonical 문서 생성

Coverage Gaps

  • Files marked NOT_READ or BLOCKED: none
  • Whole-corpus conclusions: INCOMPLETE — 아래 unseen owner/decision을 교차 lane에서 확인해야 한다.

Grep Verification

각 finding 안에 25개 quote pattern의 절대경로 명령과 observed output을 기록했다. 대표 확인:

grep -nFo -- 'core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립' raw/branch-notes/feature-keycloak-first-broker-login-flow.md
# Observed: 135:core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립

grep -nFo -- '| D2 | refresh token 저장 위치 = secure httpOnly cookie 권장' raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.md
# Observed: 280:| D2 | refresh token 저장 위치 = secure httpOnly cookie 권장
  • 검증한 verbatim quote 총 개수 V: 25
  • 일치 P: 25
  • 라인 정정 C: 0
  • 폐기 D: 0

Claim Traceability Check

  • Claim ID 연결 검사: slice에서 추출한 unique Claim ID 153개 중 152개가 branch/project note 밖의 raw source에서 lexical resolve. KC-HOST-C6 1개는 iss-claim-hostname-mismatch:142·230·247이 명시한 미아카이브 후보이며 Supporting Claim으로 사용되지 않아 BROKEN_CLAIM_REFERENCE는 none found.
  • Decision Evidence Map: 10/10 파일에서 1개씩, 총 10개 검토. 이번 lane은 raw Claim ID 존재성까지 확인했으며 각 raw claim의 의미 재감사는 범위 밖이다.
  • UNSUPPORTED_DECISION: 정합 finding과 직접 연결된 3개 축 — CVE version gate(H:338), JavaScript origins 비움(C:73·167), P2A brokering scope/zero-change(I:282). 그 밖의 project-scope·implementation caveat는 파일이 명시적으로 라벨링해 상충 finding으로 승격하지 않았다.

Cross-Lane Audit Candidates

File:line Referenced owner / decision Reason
feature-keycloak-google-redirect-uri-policy.md:202-205 single-EC2-google-federation D4/D6; public-domain-tunneling D1/D2 callback host/path와 tunnel 선택 owner가 unseen
feature-keycloak-header-spoofing-defense.md:202-206 edge-forwardauth-no-google D2/D4; nginx-auth-request; reverse-proxy-headers 부모 enforcement 위임과 신규 X-Internal-Auth-Token 계약 확인 필요
feature-keycloak-https-termination-caddy-nginx.md:232-233 reverse-proxy-headers D1; public-domain-tunneling TLS 종단 위치와 forwarded-header mode 결합 검증 필요
feature-keycloak-idp-brokering-google-client.md:189,192 account-linking-sub-vs-email; edge-forwardauth-google-federation trustEmail 소비 의미와 <kc-host> owner 확인 필요
feature-keycloak-internal-spa-direct-google-federation.md:403 미지정 CSRF/state owner 파일이 project-wide owner 부재를 주장하므로 다른 lane 확인 필요
feature-keycloak-internal-spa-direct-google-federation.md:404 docker-compose-stack / realm configuration H D10의 SMTP deployment-fact 소유권이 실제 stack owner와 겹치는지 확인 필요
feature-keycloak-internal-spa-direct-google-federation.md:407-408 account-linking-sub-vs-email D1; three-leg-trust-chain D1/D2/D5 L2-F07의 foreign-detail summary가 실제 owner와 같은지 확인 필요
feature-keycloak-internal-spa-direct-no-google.md:365-368 audience-validator; token-storage; refresh-rotation; PKCE-flow Hubs의 foreign D-row 요약과 L2-F09 token-storage owner 확인 필요
feature-keycloak-iss-claim-hostname-mismatch.md:204-209 single-EC2-no-google D3; audience-validator D6; docker-compose D3/D5 파일이 이미 owner conflict·trigger erosion·3자 재진술을 명시함

Stats

agent: wiki-research-lane
found: 10
processed: 10
dropped: 0