테마
Threat Model — Account Merge
위협 매트릭스
| # | 위협 | 영향 | 완화책 |
|---|---|---|---|
| T1 | Confused Deputy (T3) — 탈취 세션으로 타 계정 흡수 | A 데이터 view 에 X 데이터 혼입 | 호출자=survivor / 입력 이메일=흡수 방향 고정. dual PoP (세션 + verified email OTP) |
| T2 | Replay — 과거 요청/webhook 재전송 | 중복 merge | T1/T2 외부 surface 없음. OTP 5분+단일사용. Webhook X-Logi-Timestamp ±5분 + event_id dedupe. idempotency_key |
| T3 | Race / Concurrent Merge | canonical 모호 | [survivor, merged] ORDER BY id ASC FOR UPDATE. identity_links_no_cycle_trg. 2번째 tx → :already_processed |
| T4 | Stale device_secret takeover (T1) | 위장 흡수 | T1 은 각 SSO 의 fresh provider id_token 검증 필요. device_secret 단독으로 SSO 통과 불가 |
| T5 | Cross-provider email squatting (T2) | victim 가입 시 흡수 link | T2 는 양쪽 provider 모두 verified email 필요. 공격자가 mailbox 통제 못하면 불가 |
| T6 | Token takeover after merge | 흡수된 토큰 재사용 | merge tx 안에서 9-category 자격증명 즉시 revoke + jti blocklist. RP 세션은 user.merged webhook 으로 정리 |
| T7 | Cycle / Chain corruption (A→B+B→C) | canonical 모호 | DB BEFORE INSERT 트리거 reject + app MergeService.guard_cycle! + resolver MAX_DEPTH=10 |
| T8 | OAuth Code Injection / Mix-Up (RFC 6819 §4.4.1.1, RFC 8252 §8.1) | code 가로채기 | PKCE 강제 + downgrade defense (public client secret reject) + redirect URI exact-match + issuer validation + state echo |
| T9 | Refresh Token Reuse / Theft | 토큰 도난 | RT rotation + reuse detection → revoke_chain! 전체 후속 chain revoke. HMAC-SHA256 digest 저장. User-row lock |
| T10 | RP-to-RP User Correlation | cross-RP 프로파일링 | Pairwise sub (OIDC Core §8.1): SHA256(sector_id ‖ user_id ‖ app.pairwise_salt)[0..32]. subject_types_supported:["pairwise"] |
| T11 | Audit Log Tampering | 발자국 위조 | AuthenticationAuditLog SHA256 hash chain (previous_hash→current_hash) + verify_chain! + GENESIS_HASH. 한계: DB superuser 통째 재계산 가능 → 외부 immutable 스토리지 export 권장 |
STRIDE 매핑
| STRIDE | 위협 |
|---|---|
| Spoofing | T1, T4, T5, T8 |
| Tampering | T7, T11 |
| Repudiation | T11 |
| Information Disclosure | T10 |
| Denial of Service | T3 |
| Elevation of Privilege | T1, T6, T9 |
보안 컨트롤
| 컨트롤 | 적용 위협 | 구현 |
|---|---|---|
| PKCE 강제 (public client downgrade reject) | T8 | Oauth::AuthorizationsController, Oauth::ClientAuthentication L30-44 |
| Redirect URI exact-match | T8 | Oauth::RedirectUriValidator |
| Issuer validation | T8 | discovery issuer ↔ OIDC_ISSUER env |
| RT rotation + reuse chain revoke | T9 | Oauth::TokensController#rotate_refresh_token L98-101 |
| RT digest (HMAC-SHA256) | T9 | OauthAccessToken#refresh_token_digest |
| Pairwise sub | T10 | Oauth::SubjectIdentifier |
| Hash chain audit log | T11 | Authentication::AuditLogger |
| Merge canonical lock + cycle trigger | T3, T7 | MergeService, identity_links_no_cycle_trg, guard_cycle! |
| 9-category credential revoke on merge | T6 | merge transaction |
| Webhook HMAC + timestamp + event_id | T2 | RP webhook spec |
OTP 5분 + 단일사용 (consumed_at) | T2 | T3 merge OTP |
| Fresh provider id_token 검증 | T1, T4 | SSO 콜백 |
| Verified email on both providers | T5 | T2 트리거 조건 |
측정 항목
logi_merge_attempts_total{trigger=}logi_merge_failures_total{reason=conflict|cycle|contention|user_in_purge|both_pop_required}logi_t3_merge_otp_dispatches_total{outcome=sent|invalid_target|rate_limited|unverified_email}logi_webhook_delivery_failures_total{rp=, retry_count=}
cycle / both_pop_required spike → incident 격상.