40 KiB
40 KiB
Lane 3 — Keycloak Branch-Note Consistency Audit
Lane Inventory
| Path | Status | Evidence Lines | Extracted Facts |
|---|---|---|---|
raw/branch-notes/feature-keycloak-nginx-auth-request-integration.md |
READ_FULL | 1–306 | auth_request body handling, browser/API failure routing, upstream ownership edges |
raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow.md |
READ_FULL | 1–275 | bearer-token terminology, nginx decision references, discovery startup dependency |
raw/branch-notes/feature-keycloak-patterns.md |
READ_FULL | 1–205 | retired numbered naming versus retained D2 |
raw/branch-notes/feature-keycloak-pkce-flow-stages.md |
READ_FULL | 1–167 | PKCE S256 ownership and Admin Console terminology |
raw/branch-notes/feature-keycloak-public-domain-tunneling.md |
READ_FULL | 1–220 | quick versus named Cloudflare Tunnel domains and tunnel-choice ownership |
raw/branch-notes/feature-keycloak-realm-client-export.md |
READ_FULL | 1–238 | import wiring ownership, credential export assertions, PKCE terminology |
raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md |
READ_FULL | 1–254 | rotation demonstration, Max Reuse semantics, back-channel logout scope |
raw/branch-notes/feature-keycloak-refresh-token-rotation.md |
READ_FULL | 1–284 | rotation contract, family invalidation, receiving endpoint delegation |
raw/branch-notes/feature-keycloak-reverse-proxy-headers.md |
READ_FULL | 1–297 | Caddy path behavior, Keycloak proxy variables, delegated security decisions |
raw/branch-notes/feature-keycloak-single-ec2-google-federation.md |
READ_FULL | 1–370 | parent integration decisions and delegated tunnel/proxy details |
1. feature-keycloak-nginx-auth-request-integration.md
L3-F01: auth_request body behavior contradicts its configuration table
- Severity / class: High —
CONTRADICTION - Original goal / current state: line 101 says the body is not automatically copied; line 163 says nginx defaults to forwarding it.
- Claim: Both statements cannot govern the same configuration.
- Assumption:
proxy_pass_request_body offcan be omitted in the rendered config. This is falsified if every applicable location explicitly contains it. - Failure / falsification: Request bodies reach the auth upstream, causing unwanted disclosure, buffering, or size failures. Falsify with
nginx -Tand upstream request capture. - Action / why: Rewrite line 101 to say body forwarding must be disabled explicitly, and make the table canonical.
- Alternatives: (1) explicit
off, (2) intentional bounded forwarding with documented limits, (3) a separate body-less auth endpoint. - Counterargument: oauth2-proxy may ignore the body. That does not establish that nginx did not transmit it.
$ sed -n '101p;163p' 'raw/branch-notes/feature-keycloak-nginx-auth-request-integration.md'
- nginx `auth_request`는 subrequest이므로 **POST body가 자동 복제되지 않는다**. `/oauth2/auth`는 HEAD-like check이므로 body 불필요 → `proxy_pass_request_body off` 권장.
| `proxy_pass_request_body` | `off` | `O2PN-C8`, `NGAR-C8`. **생략하면 default `on`(`NGXPM-C1`) 이라 body 가 전달됨** |
L3-F02: unconditional sign-in TODO is stale after browser/API response split
- Severity / class: Medium —
STALE_SUMMARY - Original goal / current state: line 89 gives one 302 sign-in task; lines 193–194 later divide browser 302 from API 401.
- Claim: The implementation checklist can be read as applying the redirect to every protected location.
- Assumption: Implementers use the TODO before the later D9 table. Falsified if generated config and tests always preserve the location split.
- Failure / falsification: API clients receive HTML redirects, parsing failures, or loops. Verify both browser and
/api/requests. - Action / why: Restrict the TODO to browser locations and add an explicit API 401 task.
- Alternatives: (1) location split, (2) 401 everywhere with frontend login handling, (3) redirect allowlist, (4) content-negotiated behavior.
- Counterargument: The TODO may describe only creation of the named location. Its application scope nevertheless remains unstated there.
$ sed -n '89p;193p;194p' 'raw/branch-notes/feature-keycloak-nginx-auth-request-integration.md'
- [ ] named location `@oauth2_signin` 작성: `return 302 https://$host/oauth2/start?rd=$scheme://$host$request_uri;` — 등급: `planned`
| browser-facing (`location /`) | `auth_request /oauth2/auth;` | `error_page 401 = @oauth2_signin;` → 302 | D2, `O2PN-C5` |
| API/machine (`location /api/`) | `auth_request /oauth2/auth;` | `error_page 401 =401;` → **plain 401 pass-through** | D9, `O2PN-C9` |
2. feature-keycloak-oauth2-proxy-oidc-flow.md
L3-F03: locally validated JWT behavior remains labeled “token introspection”
- Severity / class: Low —
STALE_SUMMARY,NAMING_DRIFT - Original goal / current state: the TODO calls the flags an introspection mode, while its own audit says no introspection endpoint call is documented.
- Claim: The term implies a network protocol behavior not supported by the note’s evidence.
- Assumption: Readers use the label to infer latency, revocation, and firewall dependencies. Falsified by an observed outbound introspection request.
- Failure / falsification: Architecture is designed around an endpoint call that may not happen. Verify debug logs and outbound traffic.
- Action / why: Use neutral “bearer-token mode” or the verified local-validation/bypass behavior.
- Alternatives: (1) local JWT validation, (2) bearer-token bypass, (3) separately implement RFC 7662 introspection, (4) retain a neutral label pending measurement.
- Counterargument: “Introspection” might be informal internal vocabulary, but it conflicts with the protocol’s established meaning.
$ sed -n '85p;101p' 'raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow.md'
- [ ] token introspection 모드 (`--skip-jwt-bearer-tokens`, `--extra-jwt-issuers`)의 의미 정리 — 등급: `planned`
- **2026-07-17** (drift): 본 노트가 쓰는 **"token introspection 모드" 명칭이 부정확할 가능성**을 확인 — 공식 문서는 introspection endpoint 호출을 서술하지 않음. 사용자 작성 본문이라 자동 수정하지 않고 §Audit & Findings `NAMING_DRIFT` 로 남김. rename 여부는 사용자 판단.
L3-F04: three decision references are not machine-resolvable
- Severity / class: Low —
BARE_DECISION_REF, ambiguous ownership - Original goal / current state: all three passages intend to identify an owner decision but use bare slugs or quoted prose instead of the repository’s decision-reference syntax.
- Claim: Impact analysis cannot reliably bind these references to their owners.
- Assumption: Deterministic consistency tooling is part of change propagation. Falsified only if the tool and contract are changed to recognize these forms.
- Failure / falsification: Owner changes do not surface in consumer impact reports. Re-run the consistency checker after repair.
- Action / why: Replace each with
[[raw/branch-notes/<slug>]] D<n>. - Alternatives: N/A — reference syntax is fixed by
rules/consistency-contract.md. - Counterargument: A person can understand the references, but that does not make them machine-traceable.
$ sed -n '214p;228p' 'raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow.md'
| `SESSION_STORAGE_4KB_ABSENT` | session storage 공식 페이지에 `4k`/`4096`/`split` 문자열이 **0건** — 4kb cookie split 함정의 근거는 이 페이지가 아니라 **nginx 통합 페이지**([[raw/official-docs/oauth2-proxy-nginx-integration-official]], "Nginx normally only copies the first `Set-Cookie` header ... if your cookies are larger than 4kb, you will need to extract additional cookies manually")에 있다. | `raw/official-docs/oauth2-proxy-session-storage-official.md` §Usage Boundaries | D5a 의 Open Risk 에 반영 완료. 4kb 대응의 owner 는 형제 branch `feature-keycloak-nginx-auth-request-integration` `D5`. |
| **인가 거부 시 실제 응답 코드/본문** (401 vs 403 vs 로그인 루프) | D4 Open Risk 가 자인 — `O2PK-C3` 은 인가 실패 코드의 의미를 명시 안 하고, `O2PBEH-C2`~`C4` 는 authentication 단계 전용. 형제 branch `feature-keycloak-nginx-auth-request-integration` `D2` 의 `error_page` 분기가 이 값에 의존 | 허용 role 이 **없는** 사용자로 로그인 후 보호 경로 요청 → 응답 코드/본문 확인. `/oauth2/auth` subrequest 응답도 함께 확인(202/401 만 반환하는지 — `O2PE-C5` 와 대조) | `needs-confirmation` |
$ sed -n '182p' 'raw/branch-notes/feature-keycloak-realm-client-export.md'
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] `#D3` — `KC_HOSTNAME=localhost` + issuer-uri + network 계약 owner. 이 hostname 계약이 D2 의 redirect URI 값(`http://localhost/*`, `http://127.0.0.1/*`)의 전제. parent `#D3` 이 hostname/포트를 바꾸면 본 realm-export.json 의 redirect URI(D2) 를 동기화해야 함. (sibling `feature-keycloak-docker-compose-stack` 도 동일 의존을 "parent D3" 로 인용). 아울러 본 branch 의 client scope(realm 1개 + `spa-client` public + PKCE S256) 는 parent `#D5` 의 실 구현.
3. feature-keycloak-patterns.md
L3-F05: retired numbered naming remains an active D2 decision
- Severity / class: Medium —
CONTRADICTION,STALE_SUMMARY - Original goal / current state: line 60 retires numbered naming; line 134 still presents it as D2.
- Claim: A prohibited convention remains encoded as an active-looking decision.
- Assumption: D-rows denote current decisions. Falsified if D2 is structurally marked historical and excluded from owner resolution.
- Failure / falsification: New branches or consumers adopt the retired numbered hierarchy. Search inbound D2 references.
- Action / why: Move D2 to a retired-decision section or apply an explicit retired status.
- Alternatives: N/A — numbered branch hierarchy is prohibited by repository policy.
- Counterargument: The warning at line 60 is visible, but downstream D-reference semantics remain ambiguous.
- Single-finding justification: Other content is legacy-to-AP mapping; no second lane-local decision conflict was found.
$ sed -n '60p;134p' 'raw/branch-notes/feature-keycloak-patterns.md'
> **⚠️ 갱신 (2026-07-14)**: 아래 6패턴 인덱스는 **Phase 0 legacy(배치×federation 축)**. 현 실행계획 SSOT 는 [[raw/project-notes/keycloak-patterns-overview]] 의 **§Branch 분해 / 실행계획(R4)** — 인증 아키텍처 4패턴(AP1~AP4) + 19 Tier-2. 신규 작업은 hub 분해표를 따르며, 아래 슬러그는 hub §2.3 매핑대로 AP 로 re-map 대상. D2(`-{N}-{M}` numbered 명명)는 CLAUDE.md §11 위반으로 폐기(각 sub-sub 는 이미 content-descriptive 슬러그라 실제 영향은 프레이밍뿐).
- **D2** 2026-05-25: sub-sub-branch 명명은 `-{N}-{M}` dash-숫자 형식. M은 학습/구현 단계 순서.
4. feature-keycloak-pkce-flow-stages.md
L3-F06: D1 and D5 both own S256 enforcement
- Severity / class: Medium —
DUAL_OWNERSHIP - Original goal / current state: D1 and D5 both encode the S256 formula and Keycloak enforcement, with different evidence freshness.
- Claim: Two decisions own the same mutable contract.
- Assumption: Downstream notes treat a D-number as owner identity. Falsified if D5 is structurally non-normative.
- Failure / falsification: UI-label or rejection-semantics updates reach only one row. Compare inbound D1/D5 references.
- Action / why: Keep D1 canonical and turn D5 into a reference-only TODO mapping.
- Alternatives: (1) D1 owner/D5 pointer, (2) D5 owner/D1 scope summary, (3) merge rows, (4) split formula from vendor enforcement.
- Counterargument: D5 may exist only to map TODO step 2, but its current table form still looks normative.
$ sed -n '118p;122p' 'raw/branch-notes/feature-keycloak-pkce-flow-stages.md'
| D1 | PKCE method = `S256` 만 정리 대상 (`plain` 은 비교용 1줄). Keycloak client 설정에서 PKCE method 옵션(정식 UI 라벨 "PKCE method")을 S256 으로 지정 | `raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C3` (S256 공식: `BASE64URL-ENCODE(SHA256(ASCII(verifier)))`), `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C1` (OAuth 2.1: "Clients MUST use code_challenge and code_verifier ..."), `raw/official-docs/keycloak-client-pkce-method-enforcement-official.md#KC-PKCE-C1` (Admin UI 옵션 정식 명칭·위치), `#KC-PKCE-C3` (S256 선택 시 서술) | `official-standard + official-standard + official-vendor-doc` | OA21-C1 은 PKCE 사용 자체를 MUST 로 강제하지만 "S256 강제 / plain 금지" 라는 정확한 문장은 OA21-C1 인용에 포함 안 됨 — §7.5.1 예외 조건 확인 필요. 단, RFC 7636 + OAuth 2.1 종합 권고로 보면 정당. **2026-07-17 업데이트**: `KC-PKCE-C1` 이 UI 라벨 오류를 정정("Proof Key for Code Exchange Code Challenge Method" 가 아니라 "PKCE method", Capability Config 섹션)했으나, `KC-PKCE-C3` 은 "Keycloak applies... S256" 이라고만 서술 — **S256 설정 시 `code_challenge_method=plain` 요청을 실제로 거부(reject)한다는 명시적 문장은 여전히 없음**. 아래 Claims To Verify 의 "plain 메서드 요청을 거부" 항목은 `needs-confirmation` 유지 |
| D5 (TODO 표 step 2) | S256 공식 `code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))` + Keycloak client 에 S256 강제 | `raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C3` (S256 공식), `raw/official-docs/keycloak-securing-apps-overview-official.md#KC-SECAPP-C1` (Keycloak 이 OIDC/OAuth 2.0 stack 을 secure 가능) | `official-standard` + `official-vendor-doc` (Keycloak overview) | Keycloak client 의 "Proof Key for Code Exchange Code Challenge Method = S256" 정확한 admin UI 위치는 KC-SECAPP-C1 인용 범위 밖 — Keycloak Securing Applications 의 client adapter / settings 섹션 별도 인용 필요 |
L3-F07: deprecated PKCE Admin label survives in two TODOs
- Severity / class: Low —
STALE_SUMMARY - Original goal / current state: two implementation tasks use the old label; the audit identifies
PKCE methodas the actual label. - Claim: The correction has not reached the operational checklist.
- Assumption: The pinned Keycloak version uses the audited label. Falsified by the target Admin Console displaying the older text.
- Failure / falsification: Implementers cannot find or select the intended field. Inspect the target-version UI.
- Action / why: Update both TODOs to
PKCE method = S256; preserve the old label only as version history. - Alternatives: N/A — use the label observed in the pinned target version.
- Counterargument: Labels can vary by Keycloak version or locale; record that variant explicitly if confirmed.
$ sed -n '73p' 'raw/branch-notes/feature-keycloak-pkce-flow-stages.md'
- Keycloak client 설정: `Proof Key for Code Exchange Code Challenge Method = S256` 강제
$ sed -n '81p;202p' 'raw/branch-notes/feature-keycloak-realm-client-export.md'
- [ ] Client Advanced 탭: `Proof Key for Code Exchange Code Challenge Method = S256` — 등급: `planned`
- **`NAMING_DRIFT`** (Advisory, user prose 보존) — §목표·§TODO 의 라벨 "Proof Key for Code Exchange Code Challenge Method" 는 `KC-PKCE-C1` 기준 부정확. 실제 Admin Console 옵션명은 **"PKCE method"** (Capability Config 섹션 하위). 구현 시 이 라벨로 찾을 것. 사용자 작성 prose 는 verbatim 보존, 정정은 §구현 가이드 1 에 반영.
5. feature-keycloak-public-domain-tunneling.md
L3-F08: trycloudflare.com is described as both permanent and random
- Severity / class: High —
CONTRADICTION,STALE_SUMMARY - Original goal / current state: line 69 offers a permanent
<sub>.trycloudflare.com; line 77 says quick-tunnel URLs are random, while the parent repeats the static-looking form. - Claim: Quick-tunnel hostnames and named-tunnel managed DNS are conflated.
- Assumption: Google requires a stable registered redirect and the described quick-tunnel behavior applies. Falsified by an account providing a persistent named
trycloudflare.comhostname. - Failure / falsification: Restarting the tunnel invalidates the Google redirect URI. Create/restart the tunnel and compare hostnames.
- Action / why: Distinguish random quick tunnels from named tunnels backed by managed DNS; synchronize the parent summary.
- Alternatives: (1) random URL plus console updates, (2) named tunnel plus managed domain, (3) ngrok reserved domain, (4) another stable public endpoint.
- Counterargument: Product capabilities may have changed; verify against the exact account and client version.
$ sed -n '69p;77p' 'raw/branch-notes/feature-keycloak-public-domain-tunneling.md'
- [ ] **Cloudflare Tunnel 동작 확인** — `cloudflared tunnel create <name>` + `cloudflared tunnel route dns <name> <subdomain>` → 영구 도메인 (`<sub>.trycloudflare.com` 무료 또는 자체 도메인 CNAME) — 등급: `planned`
- Cloudflare Tunnel의 `trycloudflare.com` quick tunnel은 무료지만 URL이 random (ngrok와 유사). 정적 도메인 원하면 Cloudflare account + 자체 도메인 (Cloudflare DNS로 위임) + named tunnel 필요.
$ sed -n '356p' 'raw/branch-notes/feature-keycloak-single-ec2-google-federation.md'
- **비교 핵심**: P3A 대비 **추가 운영 비용은 "public 접근 가능한 callback URL"** 1가지. Google OAuth client console에 등록할 redirect_uri는 **HTTPS 필수 + localhost 제외** (Google 명시 — `http://localhost` 만 예외, IP 주소 X). 학습 환경에서는 **ngrok** (`https://<random>.ngrok-free.app` 임시 URL) 또는 **Cloudflare Tunnel** (`<name>.trycloudflare.com` 또는 자체 도메인) 사용. ngrok은 무료 plan에서 URL이 매 세션 변경 → Google client console 매번 업데이트 필요 → Cloudflare Tunnel이 학습 단계에 더 적합 (정적 도메인). **Keycloak `KC_PROXY_HEADERS=xforwarded` + `KC_HOSTNAME=<public-domain>`** 명시 안 하면 reverse proxy 뒤에서 redirect URL이 internal `keycloak:8080`으로 떨어져 깨짐.
This file is also the child-side source for L3-F15.
6. feature-keycloak-realm-client-export.md
L3-F09: import wiring remains in scope after ownership was delegated
- Severity / class: Medium —
STALE_SUMMARY,RESTATED_FOREIGN_DECISION - Original goal / current state: line 53 includes automatic container import; line 168 says this branch authors JSON only and must not restate wiring.
- Claim: Scope and ownership trace assign the same concern differently.
- Assumption: Scope bullets denote implementation responsibility. Falsified if line 53 is explicitly reclassified as a consumer assumption.
- Failure / falsification: Realm and stack branches independently change mount paths or startup flags. Inspect actual changed artifacts.
- Action / why: Keep JSON authorship here and make import wiring reference-only to the stack owner.
- Alternatives: (1) JSON/stack split, (2) this branch owns full import lifecycle, (3) dedicated import-lifecycle owner.
- Counterargument: Line 53 might describe desired behavior, not ownership; its current placement does not state that distinction.
$ sed -n '53p;168p' 'raw/branch-notes/feature-keycloak-realm-client-export.md'
- Keycloak 컨테이너 `--import-realm` 옵션으로 부트 시 자동 import
> **Trace / R3**: volume mount(`./realm-export.json:/opt/keycloak/data/import/realm-export.json`) + Keycloak 부트 command `--import-realm` 는 sibling [[raw/branch-notes/feature-keycloak-docker-compose-stack]] `#D4` 가 owner (`KC-IMPORT-C1/C2/C3/C4` 인용). 본 branch 는 **realm-export.json 저작만** — 배선은 restate 하지 않는다. 배선 계약이 바뀌면(import dir 경로/flag) 본 export 파일 배치에 영향(§엣지·의존).
L3-F10: credential export is asserted before its evidence gate
- Severity / class: High —
CONTRADICTION, unsupported certainty - Original goal / current state: line 99 states credential behavior; line 193 says the password-inclusive
usersExportclaim has no primary raw source. - Claim: Operational prose presents an unverified export mode as available.
- Assumption: Bootstrap automation may rely on exported users or passwords. Falsified by version-matched documentation and inspected export output.
- Failure / falsification: Imported users cannot authenticate, or sensitive credentials are handled under a false assumption.
- Action / why: Move credential claims to verification status until the pinned image’s help and actual export are inspected.
- Alternatives: (1) no users and separate bootstrap, (2) verified user export with secret controls, (3) Admin API provisioning, (4) disposable test credentials.
- Counterargument: The behavior may be remembered from another Keycloak version; that does not establish the pinned version.
$ sed -n '99p;193p' 'raw/branch-notes/feature-keycloak-realm-client-export.md'
- Realm export JSON은 **user password를 포함하지 않음** (보안). user는 ID/role만 export됨 → 부트 후 admin이 password 재설정 필요. 또는 realm export에서 `usersExport=true`로 별도 export 가능.
| `usersExport=true` 옵션으로 user (password 포함) export 가능 | 본 branch 진행 중 메모 — 1차 raw 미수집 | Keycloak `kc.sh export --help` 또는 admin REST API 문서 발췌 | `planned` |
This file is also implicated in L3-F04 and L3-F07.
7. feature-keycloak-refresh-rotation-and-logout.md
L3-F11: concept and execution notes both own the same rotation sequence
- Severity / class: High —
DUAL_OWNERSHIP,RESTATED_FOREIGN_DECISION - Original goal / current state: the concept note declares a concept/execution split, but both notes retain a normative D-row for the same family-invalidation flow.
- Claim: Mutable expected behavior has two decision owners.
- Assumption: D-rows are normative. Falsified if P3A D5 is structurally converted into a non-normative test procedure.
- Failure / falsification: A failed family-kill test updates only one note. Inspect incoming D4/D5 references and modification ownership.
- Action / why: Keep policy/expected behavior in the concept owner and make P3A reference it from its test procedure.
- Alternatives: (1) concept owner/execution pointer, (2) execution owner/concept principle, (3) explicit invariant/procedure split, (4) separate verified-behavior owner.
- Counterargument: Definition and demonstration are naturally repeated, but both current rows make the repeated value independently mutable.
$ sed -n '159p;240p' 'raw/branch-notes/feature-keycloak-refresh-token-rotation.md'
| D4 | rotation flow 4단계 (RT_1 사용 → invalidate → AT_2+RT_2 발급 → 재사용 시 family invalidate) | N/A — rotation ON 이면 항상 이 flow. 단 재사용 시 무효화 **범위**(단일 토큰 vs family 전체)는 verify 대상 | `raw/official-docs/keycloak-refresh-token-rotation-sessions-official.md#KC-RTROT-C2` (RT 1회 사용 후 무효 + 최신 RT 저장 강제 = flow 1~3단계 근거) + `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3` (refresh token = scope/RS bound MUST, 배경) + `UNSUPPORTED_DECISION` (4단계 "family 전체 invalidate" 는 `KC-RTROT-C6` doc text 부재) | `official-vendor-doc (1~3단계) + official-standard (배경) + UNSUPPORTED_DECISION (family invalidate)` | family invalidate 동작을 docker-compose 실측(RT_1 재사용 → RT_2 도 거부되는지)으로 확인 — Claims To Verify 에 계획됨 |
- cousin(다른 phase) [[raw/branch-notes/feature-keycloak-refresh-rotation-and-logout]] (P3A) — 본 개념 계약의 **실측·시연** 소유. 본 노트 = 개념/계약, 그쪽 = 실행.
$ sed -n '124p' 'raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md'
| D5 | rotation flow 시연 (RT_1 사용 → AT_2+RT_2 → RT_1 재사용 → family 전체 invalidate → 재로그인 강제) | N/A (rotation ON 전제의 단일 시연 시나리오). rotation OFF 면 재사용 탐지 자체가 불가 → D1 이 전제 | `raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official.md#KC-ROT-C1` (rotation = 사용된 RT 무효화 + 새 발급) + `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3`·`#OA21-C6` (RT bound MUST, code grant → AT+RT) + `raw/official-docs/oidc-client-ts-library.md#OIDCTS-C4`·`#OIDCTS-C5` (Refresh Token Grant + Silent Refresh — SPA 자동 갱신 경로) + `UNSUPPORTED_DECISION` ("RT_1 재사용 → family 전체 invalidate" 자동 동작 = `KC-ROT-C6` 문서 부재) | `official-vendor-doc (KC-ROT-C1, OIDCTS-C4/C5) + official-standard (OA21-C3/C6) + UNSUPPORTED_DECISION (family invalidate 구체 동작)` | docker-compose 실측: RT 재사용 시 4xx + RT_2 동시 invalidate(family kill) 확인 (Claims To Verify #1) |
L3-F12: delegated back-channel receiver is out of scope at its destination
- Severity / class: High —
CONTRADICTION, ambiguous ownership - Original goal / current state: the concept note delegates receiver implementation to P3A; P3A lists provider-triggered logout as an optional demonstration only.
- Claim: No note unambiguously owns the receiving endpoint.
- Assumption: “옵션 시연만” excludes backend implementation. Falsified by an explicit endpoint artifact and validation plan in P3A.
- Failure / falsification: Logout Token reception is relied upon but never built or verified.
- Action / why: Add the endpoint to P3A, remove the delegation, or create a dedicated owner.
- Alternatives: (1) implement in P3A, (2) remove from scope, (3) dedicated branch, (4) provider-side demonstration with explicit absence of receiver.
- Counterargument: The optional demonstration might include the endpoint; planned files and acceptance tests must show that.
$ sed -n '67p' 'raw/branch-notes/feature-keycloak-refresh-token-rotation.md'
- **back-channel logout 수신** backend 구현(Logout Token 수신 endpoint) — D3, P3A 실 구현 시
$ sed -n '61p' 'raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md'
- back-channel logout (Keycloak admin → 모든 session kill) — 옵션 시연만
L3-F13: Max Reuse greater than zero is both meaningless and a weaker alternative
- Severity / class: High —
CONTRADICTION, incompatible policy assumptions - Original goal / current state: one note says any value above zero makes reuse detection meaningless; the other treats one or more as weaker but viable.
- Claim: The policy models the setting with incompatible semantics.
- Assumption: Max Reuse is a bounded-use counter in the pinned version. If the field differs or is absent, both claims need replacement.
- Failure / falsification: Concurrency mitigation either disables the intended test or rejects legitimate refreshes. Test values 0 and 1 with concurrent requests.
- Action / why: Replace certainty with one measured semantics table shared by both notes.
- Alternatives: (1) zero plus refresh serialization, (2) measured bounded reuse, (3) BFF/server-side serialization, (4) defer selection until measurement.
- Counterargument: “의미 없음” may mean only “unsuitable for D5”; the note does not currently state that narrower scope.
$ sed -n '127p' 'raw/branch-notes/feature-keycloak-refresh-token-rotation.md'
- `Refresh Token Max Reuse > 0`이면 reuse detection 의미 없음
$ sed -n '120p' 'raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md'
| D1 | rotation ON + Max Reuse 0 강제 — stolen token 탐지 시연 | stolen-token 탐지를 *시연*하려면 rotation ON + Max Reuse 0. 정상 SPA 가 동시 갱신(race)으로 같은 RT 를 2회 보낼 위험이 크면 대안: Max Reuse ≥1 로 완화(단 탐지력 약화, D5 시연 불가) | `raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official.md#KC-ROT-C1` ("Revoke Refresh Token" ON = 사용된 RT revoke + 새 토큰 발급 = rotation) + `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3` (RT scope/RS bound MUST — rotation 권고 배경) + `UNSUPPORTED_DECISION` ("Refresh Token Max Reuse" 설정명 + 재사용 시 family invalidate 자동 동작 = `KC-ROT-C6` 문서 부재) | `official-vendor-doc (KC-ROT-C1) + official-standard (OA21-C3 배경) + UNSUPPORTED_DECISION (Max Reuse 0 라벨 + family invalidate)` | admin UI 실측으로 "Refresh Token Max Reuse" 필드 실재·라벨 + RT 재사용 시 family kill 동작 확인 (Claims To Verify #1) |
8. feature-keycloak-refresh-token-rotation.md
This file is the co-source for L3-F11, L3-F12, and L3-F13. No additional lane-local inconsistency remained after those shared findings; its remaining related D-rows retain explicit UNSUPPORTED_DECISION labels rather than asserting family invalidation as verified.
9. feature-keycloak-reverse-proxy-headers.md
L3-F14: executable Caddy draft strips a prefix the selected method requires
- Severity / class: High —
STALE_SUMMARY, cannot-coexist configuration - Original goal / current state: the copyable draft uses
handle_path; its later audit says method B requires prefix-preservinghandle/reverse_proxy. - Claim: The executable draft and
KC_HTTP_RELATIVE_PATH=/keycloakcannot coexist without another rewrite. - Assumption: Standard
handle_pathstripping applies and Keycloak runs at/keycloak. Falsified by a verified rewrite or root-path Keycloak configuration. - Failure / falsification: Discovery and authorization endpoints reach the wrong path and return 404. Test all OIDC endpoints through Caddy.
- Action / why: Repair the snippet itself or explicitly switch the design to method A.
- Alternatives: (1) prefix-preserving
handle, (2) method A with deliberate stripping, (3) explicit verified rewrite, (4) nginx prefix preservation. - Counterargument: Another layer may restore the prefix; no such layer is shown in the draft.
$ sed -n '125p;202p' 'raw/branch-notes/feature-keycloak-reverse-proxy-headers.md'
handle_path /keycloak/* {
> ⚠️ **method B ↔ proxy 라우팅 정합 함정 (depth 감사 2026-07-18 F1)**: §진행 중 메모의 **Caddy 초안 `handle_path /keycloak/*`** 은 `/keycloak` prefix 를 **strip** 한다 (= method A 동작). 그런데 D3 은 method B(`KC_HTTP_RELATIVE_PATH=/keycloak`, Keycloak 이 스스로 `/keycloak/*` 발급)를 채택했으므로, prefix 가 strip 된 요청이 prefix 를 기대하는 Keycloak 에 도달 → **이중 strip 으로 404**. 초안의 "Caddy 환경변수는 nginx case 와 동일" 은 *환경변수*는 맞으나 *라우팅*은 다름을 가린다. nginx `location /keycloak/ { proxy_pass http://127.0.0.1:8080; }` 는 prefix 를 **보존**하므로 method B 와 정합. **method B 를 Caddy 로 쓰려면 `handle_path`(strip) 대신 `handle`/`reverse_proxy` 로 `/keycloak` prefix 를 보존**해야 한다. Caddy vendor doc 미보존(D4 UNSUPPORTED)이라 FACT 단정 대신 §Claims To Verify 로 실측 위임.
L3-F16: parent owns exact proxy values while delegating proxy details
- Severity / class: High —
DUAL_OWNERSHIP,RESTATED_FOREIGN_DECISION - Original goal / current state: parent D4 fixes five exact variables; its ownership summary delegates proxy-header detail to the child.
- Claim: The parent contains a mutable configuration bundle that overlaps delegated owners.
- Assumption: Parent D4 is consumed as normative configuration. Falsified if it is generated from one canonical source and children cease owning values.
- Failure / falsification: A routing or security owner changes a value while the parent remains deployable and stale.
- Action / why: Keep integration invariants in the parent and reference each variable’s owner decision.
- Alternatives: (1) child owners plus parent references, (2) parent owns final bundle and children provide evidence, (3) explicit per-variable ownership matrix.
- Counterargument: An integration parent may need the assembled bundle, but then it must be the declared canonical owner rather than also delegating details.
$ sed -n '239p;301p' 'raw/branch-notes/feature-keycloak-single-ec2-google-federation.md'
| D4 | Keycloak `KC_HOSTNAME=https://kc.example.com` + `KC_HTTP_RELATIVE_PATH=/keycloak` + `KC_PROXY_HEADERS=xforwarded` + `KC_HTTP_ENABLED=true` + `KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1` | `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2`, `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C5`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C2`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C3`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C4`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C5`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C6` | `official-vendor-doc` | `KC_HTTP_RELATIVE_PATH=/keycloak` 와 `X-Forwarded-Prefix` 가 nginx → Keycloak 경로에서 OIDC discovery (`.well-known/openid-configuration`) 의 `issuer` 값을 어떻게 결정하는지 실 검증 필요 (`KC-RP-C6` 두 방법의 trade-off 미해소) |
- sub-sub-branch 관심사 위임: [[raw/branch-notes/feature-keycloak-public-domain-tunneling]](tunnel 상세) · [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]](proxy header 상세) · [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]](redirect URI 정책) · [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]](TLS termination) 가 각 관심사 detail owner.
10. feature-keycloak-single-ec2-google-federation.md
L3-F15: parent and child both decide Cloudflare-over-ngrok
- Severity / class: Medium —
DUAL_OWNERSHIP - Original goal / current state: parent D3 and child D1 both own the same provider preference, while the child supplies the selection conditions.
- Claim: Tunnel-selection authority is duplicated.
- Assumption: Both D-rows are normative and independently mutable. Falsified by a declared selection/detail split without restating the mutable preference.
- Failure / falsification: A provider-policy change updates only one branch. Inspect inbound D3/D1 references and approval ownership.
- Action / why: Make the child the selection owner and reduce the parent to required outcomes plus a decision reference.
- Alternatives: (1) child owns selection, (2) parent owns selection and child operationalizes, (3) parent owns requirements while child maps conditions to providers.
- Counterargument: Parent selection plus child implementation can be valid, but both current rows phrase the provider preference as their own decision.
$ sed -n '238p' 'raw/branch-notes/feature-keycloak-single-ec2-google-federation.md'
| D3 | 개발용 public URL 노출 수단은 Cloudflare Tunnel 우선, ngrok 차선 | `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C1`, `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C2`, `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C4`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C1`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C3`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C4`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C5` | `official-vendor-doc + official-vendor-doc + official-vendor-doc` | Cloudflare Tunnel `<UUID>.cfargotunnel.com` 도메인이 Google OAuth client redirect URI 정책 (`GOOGLE-REDIR-C2` raw IP 금지 / `C3` exact match) 을 통과하는지 직접 검증 안 됨 |
$ sed -n '106p' 'raw/branch-notes/feature-keycloak-public-domain-tunneling.md'
| D1 | 학습 환경 1순위 **Cloudflare Tunnel** (named tunnel + 정적 URL + 무료 TLS + inbound port 0) | 정적 public URL + inbound port 0 + 무료 TLS 가 필요하고 Cloudflare 에 위임한 관리 도메인 1개를 확보할 수 있을 때. 도메인이 없거나 1회성 데모면 → **D2**(ngrok) 또는 `trycloudflare.com` quick tunnel(random URL) | `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 실 검증 필요 |
This parent is also implicated in L3-F08 and L3-F16.
Grep verification
- 실제 실행해 위에 출력한
sed/grepsource lines V: 37 - byte-exact 일치 P: 37
- 라인 정정 C: 0
- 폐기 D: 0
Claim Traceability Check
- Claim ID 연결 검사: lane-local decision references를 대조했고
L3-F04의 bare references 3건을 확인했다. 외부 raw Claim ID source truth 재검증은 slice 밖이다. - Decision Evidence Map: 10개 branch note의 DEM을 검토했다. 중복 또는 stale ownership은
L3-F06,L3-F11,L3-F15,L3-F16에 기록했다. - UNSUPPORTED_DECISION: 새 unsupported decision을 사실로 승격하지 않았다. 원문의 Max Reuse, family invalidation, proxy 관련 labels는 그대로 보존했다.
Cross-lane candidates
| Source edge | External owner / decision | Reason |
|---|---|---|
feature-keycloak-nginx-auth-request-integration.md:50 |
feature-keycloak-edge-forwardauth-no-google D1 |
standalone nginx 예시와 parent Kubernetes/Traefik split의 정합성 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-nginx-auth-request-integration.md:249 |
feature-keycloak-edge-forwardauth-no-google D2 |
forwarded-header naming의 canonical owner — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-nginx-auth-request-integration.md:253 |
feature-keycloak-header-spoofing-defense D5 |
요구하는 X-Internal-Auth-Token이 현재 nginx D4에 없음 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-oauth2-proxy-oidc-flow.md:194 |
feature-keycloak-docker-compose-stack D3 |
discovery startup gating과 healthcheck 책임 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-realm-client-export.md:168 |
feature-keycloak-docker-compose-stack D4 |
realm import mount/command ownership — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-realm-client-export.md:182 |
feature-keycloak-single-ec2-no-google D3, docker-compose D3 |
hostname/redirect URI 전제와 bare sibling 귀속 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-reverse-proxy-headers.md:151 |
feature-keycloak-https-termination-caddy-nginx |
Caddy/nginx selection ownership — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-reverse-proxy-headers.md:153, :258 |
feature-keycloak-header-spoofing-defense D6 |
KC_PROXY_TRUSTED_ADDRESSES owner 정규화 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-reverse-proxy-headers.md:154 |
feature-keycloak-iss-claim-hostname-mismatch |
KC_HOSTNAME value owner와 parent D4의 중복 여부 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-single-ec2-google-federation.md:240, :279 |
first-broker/account-linking siblings | parent D5와 consume-only 구현 절의 경계 — NEEDS_CROSS_LANE_AUDIT |
feature-keycloak-single-ec2-google-federation.md:241, :301 |
feature-keycloak-google-redirect-uri-policy |
parent D6 exact URI와 delegated policy owner의 경계 — NEEDS_CROSS_LANE_AUDIT |
agent: wiki-research-lane
found: 10
processed: 10
dropped: 0