Lane Inventory
| Path |
Status |
Evidence Lines |
Extracted Facts |
raw/branch-notes/feature-keycloak-single-ec2-no-google.md |
READ_FULL |
1–342 |
3 findings |
raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff.md |
READ_FULL |
1–256 |
3 findings |
raw/branch-notes/feature-keycloak-spring-rs-audience-validator.md |
READ_FULL |
1–268 |
2 findings |
raw/branch-notes/feature-keycloak-spring-rs-role-mapping.md |
READ_FULL |
1–231 |
2 findings |
raw/branch-notes/feature-keycloak-three-leg-trust-chain.md |
READ_FULL |
1–229 |
3 findings |
raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative.md |
READ_FULL |
1–237 |
2 findings |
raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md |
READ_FULL |
1–257 |
3 findings |
raw/branch-notes/feature-security-operational-baseline.md |
READ_FULL |
1–417 |
3 findings |
raw/branch-notes/feature-keycloak-single-ec2-no-google.md
- Severity / 유형: High — ambiguous/incompatible default flow
- Source quotes:
"extra_hosts: [host.docker.internal:host-gateway]" — raw/branch-notes/feature-keycloak-single-ec2-no-google.md:146
"backend도 \issuer-uri=http://localhost:8080/...` 사용. 컨테이너 간 통신은 `network_mode: host` 또는 `extra_hosts` 사용."—raw/branch-notes/feature-keycloak-single-ec2-no-google.md:181`
- Claim: bridge-network backend 컨테이너에서
localhost는 backend 자신이다. extra_hosts는 host.docker.internal 이름을 추가할 뿐 localhost를 호스트로 바꾸지 않는다. 두 메커니즘을 동일한 해결책처럼 병기하면 기본 구성이 실행 불가능하다.
- Assumptions: backend도 Docker bridge network에서 실행되고, 별도
jwk-set-uri override가 없다.
- Failure mode: OIDC discovery/JWKS 접근 실패로 backend startup 실패 또는 전 요청 401.
- Falsification: 실제 기본이
network_mode: host, backend host-process 실행, 또는 issuer-uri=localhost와 jwk-set-uri=host.docker.internal을 명시적으로 분리한 구성이라면 비판이 무효화된다.
- Required action / Why:
feature-keycloak-iss-claim-hostname-mismatch owner에서 기본 한 가지를 고정하고 이 노트는 포인터만 유지한다. issuer 문자열 일치와 네트워크 도달성은 서로 다른 조건이기 때문이다.
- Alternatives:
- host network +
issuer-uri=localhost
- bridge +
issuer-uri=localhost + 명시적 jwk-set-uri=host.docker.internal
- front/backchannel 동적 URL을 명시적으로 구성
- Counterargument:
extra_hosts로 호스트 접근은 가능하다. 다만 접근 hostname은 host.docker.internal이므로, 현재 적힌 localhost discovery URI와 자동으로 결합되지는 않는다.
- Self-grep:
L4-F02 — aud validator owner 정정 후 컴포넌트 표가 옛 owner를 계속 지목
- Severity / 유형: Medium —
STALE_SUMMARY
- Source quotes:
- Claim: 같은 문서가 owner를
audience-validator로 정정한 뒤 실행 표에서는 여전히 deferred RBAC 문서의 D2를 가리킨다.
- Assumptions: line 110의 2026-07-18 owner split이 최신 결정이다.
- Failure mode: 구현자가 role-mapping의 복제된 validator를 수정하고 정본과 다시 갈라진다.
- Falsification: role-mapping D2가 여전히 공동 owner라면 Single-Owner 선언 자체를 철회해야 한다.
- Required action / Why: line 215와 동일 요약들을
[[...spring-rs-audience-validator]] D1로 바꾸고 role-mapping은 RBAC D4–D6만 가리킨다.
- Alternatives: RS/audience 전체를 role-mapping으로 다시 이관할 수도 있으나 두 문서 중 하나만 owner여야 한다.
- Counterargument: 표가 역사적 child 목록일 수 있다. 그러나 셀 문구가 현재 구현 근거를 직접 지목하므로 역사 기록으로 읽히지 않는다.
- Self-grep:
L4-F03 — PKCE 구현 순서가 parent와 실행 owner에서 반대
- Severity / 유형: Medium —
CONTRADICTION
- Source quotes:
"vanilla JS는 manual fetch + \crypto.subtle` 기반 PKCE 구현 우선"—...single-ec2-no-google.md:182`
"\oidc-client-ts` 우선, manual은 별도 단계."—raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md:106`
- Claim: parent는 manual-first, 실행 owner는 library-first를 결정한다.
- Assumptions: 두 문구 모두 실제 코딩 순서를 뜻한다.
- Failure mode: 작업 순서·완료 조건·테스트 baseline이 달라진다.
- Falsification: 전자를 사전 학습 순서, 후자를 repo 구현 순서로 명시하면 양립 가능하다.
- Required action / Why: 실행 순서는 child D1을 정본으로 두고 parent D4를 “manual 사전 학습 선택지”로 재분류한다.
- Alternatives: manual spike → 폐기 → library 구현, 또는 library E2E → manual 비교 실습.
- Counterargument: 결국 둘 다 수행하므로 충돌이 작다. 하지만 단계별 구현 branch의 착수 순서와 acceptance criteria에는 직접 영향을 준다.
- Self-grep:
raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff.md
L4-F04 — “SPA Direct, BFF 미채택” 기본이 실제로는 TMB를 요구
- Severity / 유형: High — incompatible architecture default
- Source quotes:
"P2A 권장 조합 = access_token: 메모리 + refresh_token: secure httpOnly cookie" — ...spa-token-storage-tradeoff.md:140
"사실상 최소 Token-Mediating Backend(TMB). \"순수 no-server SPA Direct\" 와의 긴장" — ...spa-token-storage-tradeoff.md:165
- Claim: 브라우저 JS는 HttpOnly cookie를 설정할 수 없다. D1을 구현하려면 backend가 refresh token을 받아
Set-Cookie하고 /refresh를 소유해야 하므로 순수 SPA Direct가 아니다.
- Assumptions: “SPA Direct”가 token endpoint를 브라우저가 직접 호출하고 server-side token custody가 없다는 뜻이다.
- Failure mode: HttpOnly 저장이 구현되지 않거나, 암묵적으로 TMB를 추가하면서 CSRF·rotation·logout 계약이 빠진다.
- Falsification: 프로젝트 taxonomy가 Resource Server의
/refresh 추가를 여전히 AP1로 명시적으로 허용하면 무효화된다.
- Required action / Why: D1을 순수 AP1과 TMB 변형으로 분리하고 기본을 하나 고정한다. token custody가 인증 패턴을 바꾸기 때문이다.
- Alternatives:
- 순수 SPA: 양 토큰 memory-only + reload 재로그인
- TMB: refresh token HttpOnly cookie +
/refresh
- BFF: 모든 token server-side
- Counterargument: 기존 Resource Server에 endpoint 하나만 추가하면 된다. 그래도 refresh token 보유·CSRF·cookie lifecycle 책임이 추가되어 trust boundary가 달라진다.
- Self-grep:
L4-F05 — D3·D6 근거 상태를 해소한 뒤 Claims To Verify가 계속 UNSUPPORTED로 기록
- Severity / 유형: Medium —
STALE_SUMMARY
- Source quotes:
"D3 를 브라우저·토폴로지 조건부로 **정밀화**" — ...spa-token-storage-tradeoff.md:131
"D6 를 \UNSUPPORTED` 에서 해소"—...spa-token-storage-tradeoff.md:132`
"본 branch D3 의 근거가 UNSUPPORTED" — ...spa-token-storage-tradeoff.md:214
"D6 가 UNSUPPORTED" — ...spa-token-storage-tradeoff.md:216
- Claim: active decision과 검증 큐가 같은 근거 상태를 다르게 표시한다.
- Assumptions: 2026-07-18 decision update가 최신이다.
- Failure mode: 이미 조사한 근거를 재조사하거나, 실제로 남은 runtime 검증과 문헌 근거 갭을 혼동한다.
- Falsification: Claims 행이 과거 상태를 보존하는 역사 섹션이면 날짜와 resolved 표기가 있어야 한다.
- Required action / Why: D3는 “근거 확보, topology별 E2E planned”, D6는 “inference 근거 확보, 저장 동작 E2E planned”로 분리한다.
- Alternatives: 해소된 행을 resolved archive로 이동하고 새 empirical claim만 남긴다.
- Counterargument: 구현 검증은 아직 안 됐다. 그렇더라도 “문헌 unsupported”와 “runtime unverified”는 다른 상태다.
- Self-grep:
L4-F06 — cookie-CSRF 구현 위임 대상에 소유 Decision이 없음
- Severity / 유형: Medium — ambiguous owner / owner-less concern
- Source quotes:
- Claim: storage 문서는 CSRF 실 구현을 audience-validator “계열”로 보냈지만, 대상 문서는 bearer JWT 검증만 소유하며 CSRF D-row가 없다. 포인터도
D<n> 없이 광범위하다.
- Assumptions: HttpOnly refresh cookie/TMB 변형을 실제 선택한다.
- Failure mode: bearer API의
csrf.disable()를 cookie refresh endpoint에도 적용하거나, CSRF token 발급·검증을 아무 branch도 구현하지 않는다.
- Falsification: 별도 unseen owner가 CSRF endpoint 계약을 이미 소유한다면 그 owner D-row를 직접 가리키면 된다.
- Required action / Why: TMB/BFF refresh endpoint owner에 CSRF Decision을 두고 여기서는 정확한 D 포인터만 유지한다.
- Alternatives: audience-validator가 범위를 확장할 경우 명시적 D-row·cookie endpoint scope·음성 테스트를 추가한다.
- Counterargument: 같은
SecurityFilterChain에서 구현할 수 있다. 구현 위치와 결정 owner는 별개이며 bearer와 cookie 요청은 CSRF 위협 모델이 다르다.
- Self-grep:
raw/branch-notes/feature-keycloak-spring-rs-audience-validator.md
L4-F07 — “custom validator 필수” 요약이 최신 property-first 선택 조건과 어긋남
- Severity / 유형: Medium —
STALE_SUMMARY
- Source quotes:
"custom \OAuth2TokenValidator`를 추가해야 하는 이유"—...spring-rs-audience-validator.md:17`
"단일 audience** 면 property 한 줄이 단순·안전(권장)" — ...spring-rs-audience-validator.md:183
- Claim: 목적·본문은 custom validator를 필수로 표현하지만 최신 구현 가이드는 단일 audience에서 Boot
audiences property를 권장 baseline으로 둔다.
- Assumptions: 본 프로젝트는 우선 단일 expected audience다.
- Failure mode: 불필요한 custom decoder wiring에서 default issuer/timestamp validator를 덮어쓰거나 누락한다.
- Falsification: 다중 audience/
azp 조건부 검증이 고정 요구라면 custom이 필수일 수 있다.
- Required action / Why: “aud 검증은 필수, 구현 방식은 단일=property / 복합=custom”으로 목표·TODO·면접 문장을 동기화한다.
- Alternatives: custom 방식은 비교 학습 예제로 남긴다.
- Counterargument: custom 구현 학습이 목적이다. 학습 예제라는 점과 production 기본을 분리하면 된다.
- Self-grep:
L4-F08 — expected audience가 backend-client-id와 spa-client로 갈림
- Severity / 유형: High —
CONTRADICTION
- Source quotes:
"backend client_id 로 \aud` 를 검증"—...spring-rs-audience-validator.md:156`
"\aud=spa-client`를 강제했습니다"—...spring-rs-role-mapping.md:38`
- Claim: 정본은 API/backend용 audience를 요구하지만 소비 문서는 SPA public client ID를 expected audience로 고정한다.
- Assumptions:
backend-client-id와 spa-client는 별도 client다.
- Failure mode: 정상 토큰이 모두 401이 되거나, 잘못된 resource audience를 허용해 cross-resource token reuse 방어가 무효화된다.
- Falsification: API audience 식별자를 의도적으로
spa-client와 동일하게 정의했다면 두 문구는 같은 값을 뜻할 수 있다. 이 경우 owner가 그 동일성부터 명시해야 한다.
- Required action / Why: realm/client-export owner와 함께 expected audience 심볼 하나를 고정하고 mapper·validator·음성 테스트에 동일하게 사용한다.
- Alternatives: backend client ID, API audience URI, 또는 의도적으로 SPA client ID를 API audience로 재사용.
- Counterargument: Keycloak public client가 access token audience일 수 있다. 그렇다면 “backend client_id mapper 필수” 결정이 잘못된 것이므로 둘 중 하나를 고쳐야 한다.
- Self-grep:
raw/branch-notes/feature-keycloak-spring-rs-role-mapping.md
L4-F09 — RS/audience 위임 선언 뒤 active scope·TODO에서 세부를 계속 재명세
- Severity / 유형: Medium —
RESTATED_FOREIGN_DECISION
- Source quotes:
- Claim: owner 정리는 정확하지만 제목·WHY·In scope·TODO가 custom validator, issuer, decoder를 실행 항목으로 계속 보유한다.
- Assumptions: line 19/128의 owner 이관이 최신이다.
- Failure mode: audience 구현이 두 branch에서 독립 진화하며 audience 값과 wiring이 갈라진다.
- Falsification: 이 파일이 역사적 migration snapshot이라면 active scope/TODO가 아니라 Audit 섹션에 있어야 한다.
- Required action / Why: 파일을 RBAC-only로 축소하고 RS-common은
[[...audience-validator]] D1/D6 — 1줄 요약만 남긴다.
- Alternatives: 반대로 전체 RS를 이 파일로 합치고 audience-validator를 폐기할 수 있으나 Single-Owner는 하나여야 한다.
- Counterargument: 구현 편의를 위한 fold-in이다. 세부 복제는 owner 진화 시 현재처럼
aud 값이 갈라지는 원인이 된다.
- Self-grep:
L4-F10 — realm-role-only 결정의 “단일 client” 전제가 4-pattern/client base와 stale
- Severity / 유형: Medium — assumption drift /
STALE_SUMMARY
- Source quotes:
"단일 client 환경(client-level role 분리 의미 적음)" — ...spring-rs-role-mapping.md:122
"4 패턴 통합 base 로 확장 시 realm 은 공유 1개 유지, client 는 패턴당 1개" — ...single-ec2-no-google.md:199
- Claim: RBAC D4의 선택 조건은 client가 하나라는 전제인데 현재 배포 base는 패턴별 여러 client를 둔다.
- Assumptions: deferred authZ를 4개 패턴 통합 base에 적용할 예정이다.
- Failure mode: 패턴별 권한 분리가 필요한데 realm role이 모든 client에 전역 적용되거나, 반대로 필요한 공통 role이 중복 정의된다.
- Falsification: RBAC가 모든 client에 의도적으로 동일한 realm-global 정책이면 유지 가능하다.
- Required action / Why: D4 선택 조건을 “realm-global 권한” 대 “client-specific 권한”으로 재작성하고 client 수를 근거로 삼지 않는다.
- Alternatives: realm-only,
resource_access only, 공통 realm role + 민감 endpoint client role 병행.
- Counterargument: authZ는 deferred라 지금 결정할 필요가 없다. 그렇다면 “단일 client라 의미 없음”이라는 확정 이유도 deferred로 낮춰야 한다.
- Self-grep:
raw/branch-notes/feature-keycloak-three-leg-trust-chain.md
L4-F11 — 3-leg 표기에는 Backend가 없지만 실제 Hop 3의 검증자는 Backend
- Severity / 유형: Medium — ambiguous trust-chain taxonomy
- Source quotes:
"P2A는 2-leg trust (Browser ↔ Keycloak)였으나, P2B는 **3-leg trust** (Browser ↔ Keycloak ↔ Google)." — ...three-leg-trust-chain.md:33
"**Hop 3: SPA → Backend**" — ...three-leg-trust-chain.md:47
- Claim: WHY의 actor chain은 Browser–Keycloak–Google만 표시하지만 계약 표는 Google→Keycloak→SPA→Backend의 세 hop과 네 actor를 다룬다.
- Assumptions: “leg”가 실제 검증 hop을 뜻한다.
- Failure mode: trust-boundary 그림에서 backend validator가 빠지거나, Browser↔Keycloak leg와 Keycloak→SPA 발급 leg가 서로 다른 방식으로 계산된다.
- Falsification: “3-leg”가 federation actor 수를 뜻한다면 Hop 3 명칭을 다른 체계로 분리해야 한다.
- Required action / Why: “3-hop / four-party validation chain”으로 정의하거나 federation chain과 API validation chain을 별도 도식으로 나눈다.
- Alternatives:
Google→Keycloak, Keycloak→SPA, SPA→Backend를 canonical hop 정의로 고정.
- Counterargument: 제목은 학습용 약칭이다. 신뢰 경계 owner 문서에서는 약칭도 verifier 누락을 만들지 않도록 정의가 필요하다.
- Self-grep:
L4-F12 — 면접 답변은 Keycloak nonce 자동 처리를 사실로 말하지만 D3는 미검증
- Severity / 유형: Medium — evidence-status contradiction
- Source quotes:
"\nonce`를 확인합니다."—...three-leg-trust-chain.md:36`
"Keycloak 의 *자동 처리 동작*은 미검증" — ...three-leg-trust-chain.md:120
- Claim: normative OIDC RP 의무는 확보됐지만 Keycloak adapter가 해당 요청/검증을 자동 수행하는지는 아직 확인하지 않았다. 면접 문장은 이 구분을 제거한다.
- Assumptions: line 36은 추후 파생 가능한 답변 초안이다.
- Failure mode: 구현 관측 없이 Keycloak-specific 동작을 외부에 확정적으로 주장한다.
- Falsification: Keycloak source, 공식 IdP 문서 또는 HAR로 nonce 송신·대조가 확인되면 승격 가능하다.
- Required action / Why: 검증 전에는 “OIDC RP가 nonce를 검증해야 하며, Keycloak 자동 처리는 확인 예정”으로 제한한다.
- Alternatives: CV6을 닫은 뒤 현재 문장 복원.
- Counterargument: 표준 준수 제품이라 예상 가능하다. 표준이 요구하는 것과 특정 버전·설정이 실제 수행하는 것은 별도 증거다.
- Self-grep:
L4-F13 — out-of-scope claim/linking 정책을 구체 검증 항목으로 재진술
- Severity / 유형: Medium —
RESTATED_FOREIGN_DECISION
- Source quotes:
"claim mapping — [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]]" — ...three-leg-trust-chain.md:57
"Account Linking — [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]]" — ...three-leg-trust-chain.md:58
"\email_verified`, `hd`, Account Linking 결정"—...three-leg-trust-chain.md:95`
- Claim: 이 노트는 mapper/account-linking을 위임하면서 내부 hop 표에서는
email_verified·hd·linking을 Keycloak이 검증하는 고정 정책처럼 적는다. 이들은 설정/flow 의존이며 owner D 포인터도 없다.
- Assumptions: 기본 brokering만으로 세 항목이 모두 강제되지는 않는다.
- Failure mode:
hd enforcement 또는 verified-email linking guard가 구성되지 않았는데 trust check가 존재한다고 오인한다.
- Falsification: unseen owners가 세 항목을 명시적으로 강제하고 이 문서가 정확한 D-row를 가리키면 1줄 요약으로 유지 가능하다.
- Required action / Why: row를 “First Broker Login + mapper 정책(config-dependent)”으로 낮추고 각 owner D-row만 연결한다.
- Alternatives: 이 검증들을 본 문서가 소유하려면 out-of-scope 선언을 제거하고 Single-Owner를 재배치해야 한다.
- Counterargument: 단순 요약이다. 현재 요약은 어떤 검사가 실제 활성인지까지 암시하므로 메커니즘 복제에 해당한다.
- Self-grep:
raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative.md
L4-F14 — P3A를 nginx+oauth2-proxy edge-auth 패턴으로 잘못 귀속
- Severity / 유형: High —
CONTRADICTION
- Source quotes:
"nginx+oauth2-proxy 확정(P3A)" — ...traefik-forwardauth-alternative.md:169
"client (vanilla JS SPA via nginx static)" / "Browser → Keycloak → Backend Resource Server JWT validation" — ...single-ec2-no-google.md:33
- Claim: Traefik 문서는 P3A를 edge ForwardAuth 구성으로 고정하지만 P3A owner는 SPA-direct PKCE + backend JWT validation이며 nginx는 static server다.
- Assumptions: P3A 최신 재매핑은 AP1 + single-EC2 deployment base다.
- Failure mode: oauth2-proxy를 잘못 추가해 browser token custody, backend validation 책임, 인증 redirect 흐름이 모두 바뀐다.
- Falsification: “AP4 edge-auth를 single-EC2 위에 올린 별도 실행 변형”을 뜻했다면 P3A가 아니라 그 조합으로 명명해야 한다.
- Required action / Why: nginx+oauth2-proxy는 P1A/AP4 owner로 돌리고, single-EC2는 배포 축으로만 참조한다.
- Alternatives:
AP1-on-single-EC2와 AP4-on-single-EC2를 별도 matrix row로 둔다.
- Counterargument: 같은 EC2에서 oauth2-proxy도 실행할 수 있다. 물리 배포 가능성과 인증 패턴 정체성은 별도 축이다.
- Self-grep:
L4-F15 — redirect를 middleware가 생성한다고 적었지만 D3는 auth server 응답 전달로 정의
- Severity / 유형: Medium —
STALE_SUMMARY
- Source quotes:
"middleware가 직접 302를 발급" — ...traefik-forwardauth-alternative.md:97
"비 2XX → 인증 서버 응답 그대로 client 에 반환" — ...traefik-forwardauth-alternative.md:112
- Claim: ForwardAuth middleware가 redirect를 생성하는 것과 auth service의 302를 전달하는 것은 다른 책임 모델이다.
- Assumptions: D3의 vendor-backed contract가 정본이다.
- Failure mode: redirect loop나
Location 손실을 Traefik 설정 문제로만 진단하고 oauth2-proxy 응답을 점검하지 않는다.
- Falsification: 사용 중인 auth plugin이 Traefik 내부에서 직접 redirect를 생성한다면 별도 D10 경로로 분리해야 한다.
- Required action / Why: line 97을 “auth server의 302를 Traefik이 그대로 전달”로 갱신한다.
- Alternatives: oauth2-proxy 외부 서버와 in-process OIDC plugin의 redirect owner를 표에서 분리한다.
- Counterargument: browser 입장에서는 Traefik에서 302가 온다. 그러나 장애 분석에는 302 생성 주체와 전달 주체 구분이 필요하다.
- Self-grep:
raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md
L4-F16 — relative /api/me·/token URL이 static-only nginx topology에서 잘못된 origin으로 감
- Severity / 유형: High —
CONTRADICTION
- Source quotes:
"fetch('/api/me', { headers: { Authorization: 'Bearer ' + user.access_token } })" — ...vanilla-js-spa-pkce.md:176
"manual \fetch('/token'"—...vanilla-js-spa-pkce.md:190`
"nginx는 단순 static 파일 서빙" — ...single-ec2-no-google.md:159
- Claim: SPA origin이
http://localhost이고 nginx가 static-only이면 두 relative URL은 nginx:80으로 향한다. 문서의 backend:8081·Keycloak:8080 직노출 topology와 양립하지 않는다.
- Assumptions: nginx에
/api·/token reverse proxy가 없다.
- Failure mode: API는 404, token exchange는 404/405가 되어 E2E가 시작되지 않는다.
- Falsification: 실제 nginx config가 두 path를 proxy한다면 parent의 static-only·3-port/CORS 계약이 stale다.
- Required action / Why: direct topology면 absolute
http://localhost:8081/api/me와 discovered Keycloak token endpoint를 사용하고, same-origin topology면 nginx proxy를 owner 결정으로 승격한다.
- Alternatives:
- 3-port direct + CORS
- nginx
/api·OIDC proxy + same-origin
- runtime config에서 endpoint base URL 주입
- Counterargument: dev proxy가 상대 URL을 처리할 수 있다. 현재 산출물은 vanilla static nginx이며 dev proxy가 명세에 없다.
- Self-grep:
L4-F17 — oidc-client-ts default가 localStorage라는 단정과 “미증명” 상태가 공존
- Severity / 유형: Medium — evidence-status
CONTRADICTION
- Source quotes:
"oidc-client-ts 기본은 \WebStorageStateStore` (localStorage)."—...vanilla-js-spa-pkce.md:98`
"default store 종류(localStorage 여부)는 본 Sources 로 미증명" — ...vanilla-js-spa-pkce.md:168
- Claim: 같은 파일이 library default를 사실과 미확인으로 동시에 표시한다.
stateStore와 userStore default도 구분되지 않았다.
- Assumptions: library version별 default가 달라질 수 있고 현재 raw source가 이를 고정하지 않는다.
- Failure mode: 불필요하거나 잘못된 override로 redirect transaction state가 소실되거나, 실제 token이 persistent storage에 남는다.
- Falsification: pin된 oidc-client-ts 버전 공식 API/default 문서가 localStorage를 명시하면 첫 문장을 근거화할 수 있다.
- Required action / Why: 검증 전 line 98을 inference로 낮추고 두 store의 default와 token 저장 여부를 각각 확인한다.
- Alternatives: 명시적 custom store로 version default에 의존하지 않되 full-page redirect state 생존 테스트를 추가한다.
- Counterargument: 어차피 memory override를 쓸 예정이다.
stateStore까지 memory로 바꾸면 callback state가 사라질 수 있어 default 확인이 여전히 필요하다.
- Self-grep:
L4-F18 — iframe silent SSO는 out-of-scope인데 automaticSilentRenew 메커니즘은 미결정
- Severity / 유형: Medium — ambiguous flow/default
- Source quotes:
"iframe 기반 silent SSO (deprecated, 대신 refresh token 사용)" — ...vanilla-js-spa-pkce.md:58
"어느 메커니즘이 default 인지 본 인용 범위 밖" — ...vanilla-js-spa-pkce.md:217
- Claim: iframe을 제외하고 refresh grant를 채택했다고 쓰면서 실제 library가 iframe과 refresh grant 중 무엇을 쓰는지 확인하지 않았다.
- Assumptions: E2E acceptance에는 reload·expiry 갱신 경로가 포함된다.
- Failure mode: Safari/cross-site에서 silent renew 실패, reload 후 in-memory user 복구 실패, 또는 rotation 시연이 실행되지 않는다.
- Falsification: pin된 library 설정과 network trace로 refresh-token grant만 사용함을 확인하면 out-of-scope 선언과 정합해진다.
- Required action / Why: refresh grant를 baseline으로 고정하고 필요한 scope/client 설정·reload bootstrap을 명세하거나 iframe을 조건부 경로로 다시 scope에 넣는다.
- Alternatives: memory-only + 명시적 재로그인.
- Counterargument: library가 런타임에 적절한 방식을 택한다. 브라우저 정책에 따라 결과가 달라지므로 학습 E2E에는 결정적 기대 동작이 필요하다.
- Self-grep:
raw/branch-notes/feature-security-operational-baseline.md
L4-F19 — Phase C2 완료 뒤 active 구현 표가 codes·CORS·redaction을 계속 미구현으로 표시
- Severity / 유형: High —
STALE_SUMMARY
- Source quotes:
"production enum \OperationalError` 미구현"—...feature-security-operational-baseline.md:222`
"CorsSettings\ 가 `["*"]`+`allowCredentials=true` 조합을 startup 거부하지 않음"—...feature-security-operational-baseline.md:269`
"token leak contract test" (planned) — ...feature-security-operational-baseline.md:279
"12 fine-grained AUTH/AUTHZ/INTERNAL codes" — ...feature-security-operational-baseline.md:360
"token/PII redaction" — ...feature-security-operational-baseline.md:363
"CORS wildcard+credentials 정적 거부" — ...feature-security-operational-baseline.md:365
CODE_GRANULARITY_DRIFT ... RESOLVED — ...feature-security-operational-baseline.md:372
- Claim: Phase C2 as-built 표는 세 항목을 locally verified로 승격했지만 앞선 Implementation Specification은 여전히 planned/미구현 상태다.
- Assumptions: Phase C2 표가 최신 코드 대조 결과다.
- Failure mode: 동일 기능을 재구현하거나 canonical 추출에서 구현 사실을 누락하고, 현재 테스트 범위를 잘못 판단한다.
- Falsification: Phase C2 표가 이후 revert된 상태라면 해당 표를 downgrade해야 한다.
- Required action / Why: active 구현 가이드와 Claims를 Phase C2 as-built에 맞추고 과거 상태는 Audit history로만 남긴다.
- Alternatives: 각 오래된 표에 “pre-2026-06-08 historical” 라벨을 붙인다.
- Counterargument: 아래 Audit에서 해소 사실을 찾을 수 있다. 상단 active specification과 검증 큐가 stale이면 소비자는 끝까지 읽지 않는 한 반대 결론을 얻는다.
- Self-grep:
L4-F20 — JWT decoder와 clock-skew의 현재 메커니즘이 세 가지 상태로 기록
- Severity / 유형: High —
CONTRADICTION
- Source quotes:
"auto-config** (custom decoder 미작성)" — ...feature-security-operational-baseline.md:197
"custom \JwtDecoder` bean"—...feature-security-operational-baseline.md:364`
"60s 는 코드에 명시되지 않고 Spring default leeway 에 의존" — ...feature-security-operational-baseline.md:374
- Claim: active guide는 auto-config/default skew, Phase C2 표는 custom decoder와 explicit skew, 후속 Audit bullet은 다시 명시 설정 부재라고 말한다.
- Assumptions: 세 진술 모두 같은 2026-06-08 이후 코드 상태를 설명한다.
- Failure mode: 잘못된 bean을 수정하거나 default drift 위험·startup IdP 의존성·validator chain 테스트 범위를 오판한다.
- Falsification:
SupplierJwtDecoder가 custom wrapper이지만 내부적으로 auto-config/default skew를 그대로 사용한다면 line 364의 “explicit clock skew” 표현이 잘못된 것이다.
- Required action / Why: 실제 code owner에서
JwtDecoderConfig의 validator chain을 대조한 뒤 한 상태만 남긴다.
- Alternatives:
- custom decoder + explicit
JwtTimestampValidator(60s)
- auto-config + vendor default 60s
- Counterargument: custom wrapper와 auto-config를 함께 쓸 수 있다. 그래도 “custom decoder 미작성”과 “custom bean locally verified”는 동시에 참일 수 없다.
- Self-grep:
L4-F21 — public-path 검증 계약이 폐기된 reflection 방식과 구현된 env-snapshot 방식을 함께 유지
- Severity / 유형: High —
STALE_SUMMARY / incompatible verification command
- Source quotes:
"SecurityFilterChain.getFilters()\ introspection"—...feature-security-operational-baseline.md:305`
"gradle task 미존재" — ...feature-security-operational-baseline.md:318
"public path snapshot gate" (locally-verified) — ...feature-security-operational-baseline.md:366
- Claim: 구현 가이드 line 243–250은 env-based snapshot으로 정정됐지만 Test Contract와 Claims는 reflection 및 task 부재를 계속 전제로 한다.
- Assumptions: Phase C2의
verifyPublicPathSnapshot task가 현행이다.
- Failure mode: 존재하는 task를 다시 작성하거나 reflection coverage를 기대해 Java hard-coded
permitAll()까지 탐지된다고 오판한다.
- Falsification: task가 이후 제거됐다면 Phase C2와 구현 가이드를 downgrade해야 한다.
- Required action / Why: Test Contract와 Claims를 env
SECURITY_PUBLIC_PATHS snapshot command·승인 flag·hard-coded permitAll() 미검출 한계로 동기화한다.
- Alternatives: 별도 reflection/behavioral test를 추가해 env gate의 blind spot을 보완한다.
- Counterargument: line 243에 정정 설명이 있다. 하지만 실행 명령을 제공하는 §테스트 계약과 active Claims가 반대 상태여서 운영자가 잘못된 검증을 수행한다.
- Self-grep:
Cross-lane owner edges
| L4 finding |
Unseen owner / governing source |
필요한 대조 |
상태 |
| L4-F01 |
feature-keycloak-iss-claim-hostname-mismatch D6, feature-keycloak-docker-compose-stack |
bridge/host network 기본과 issuer-uri/jwk-set-uri 조합 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F02, F10, F14 |
raw/project-notes/keycloak-patterns-overview F3/F5/§8 |
최신 AP taxonomy·owner split·single-EC2 deployment 축 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F04, F06 |
feature-keycloak-internal-spa-direct-no-google, feature-keycloak-bff-vs-spa-direct |
TMB 허용 여부와 cookie/CSRF owner |
NEEDS_CROSS_LANE_AUDIT |
| L4-F08 |
feature-keycloak-realm-client-export |
실제 client IDs와 Audience mapper target |
NEEDS_CROSS_LANE_AUDIT |
| L4-F10 |
feature-keycloak-realm-client-export D5 |
realm roles 대 client roles의 발급 계약 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F12, F13 |
feature-keycloak-first-broker-login-flow, feature-keycloak-idp-mappers-claim-to-role, feature-keycloak-account-linking-spa-ux |
nonce 자동 처리·email_verified·hd·linking 실제 정책 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F14, F15 |
feature-keycloak-edge-forwardauth-no-google, feature-keycloak-oauth2-proxy-oidc-flow |
AP4 owner 및 302 생성/전달 주체 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F18 |
feature-keycloak-refresh-token-rotation, feature-keycloak-refresh-rotation-and-logout |
refresh grant·rotation·reload 복구 계약 |
NEEDS_CROSS_LANE_AUDIT |
| L4-F19–F21 |
wiki/projects/ca-tmpl/security-baseline-jwt-actuator-secrets.md + ca-tmpl code owner |
Phase C2 as-built와 canonical/current code 정합 |
NEEDS_CROSS_LANE_AUDIT |
Claim Traceability Check
- Claim ID 연결 검사: 8개 branch-note의 Supporting Claims 구조를 모두 확인했다. exact slice 밖 raw-source Claim 본문은 재열람하지 않았으므로 외부 Claim byte 검증은 범위 밖이다. slice 내부 owner 의미 오귀속은 L4-F02·F06·F09로 보고했다.
- Decision Evidence Map: 8개 모두 검토. 최신 D-row와 본문/소비자 요약의 semantic drift는 L4-F02·F03·F05·F07–F10·F12·F19–F21에 반영했다.
- UNSUPPORTED_DECISION: 새 미라벨 결정으로 확정한 건은 없으며, 기존 label을 무시한 상위 요약 과장은 L4-F12에 포함했다.
Grep verification
- 실행한
grep -nFo self-check: 25회
- 검증한 verbatim quote fragment V: 55
- 일치 P: 55
- 라인 정정 C: 0
- 폐기 D: 0
- 모든 command exit code: 0
- 이 lane은 파일 수정 명령을 실행하지 않았다. 공유 worktree에는 선행 변경이 존재하므로 workspace-clean 주장은 하지 않는다.
Stats