Skip to content

Threat Model — Account Merge ​

Threat matrix ​

#ThreatImpactMitigation
T1Confused Deputy (T3) — absorb another account with a stolen sessionX's data mixed into A's data viewThe caller is fixed as the survivor and the input email fixes the absorption direction. dual PoP (session + verified email OTP)
T2Replay — resend a past request/webhookduplicate mergeno external surface for T1/T2. OTP 5 min + single-use. Webhook X-Logi-Timestamp ±5 min + event_id dedupe. idempotency_key
T3Race / concurrent mergeambiguous canonical[survivor, merged] ORDER BY id ASC FOR UPDATE. identity_links_no_cycle_trg. 2nd tx → :already_processed
T4Stale device_secret takeover (T1)impersonated absorptionT1 requires verifying each SSO's fresh provider id_token. A device_secret alone cannot pass SSO
T5Cross-provider email squatting (T2)absorption link on the victim's signupT2 requires a verified email on both providers. Impossible unless the attacker controls the mailbox
T6Token takeover after mergereuse of an absorbed tokenwithin the merge tx, immediately revoke 9 categories of credentials + jti blocklist. RP sessions are cleaned up via the user.merged webhook
T7Cycle / chain corruption (A→B+B→C)ambiguous canonicalDB BEFORE INSERT trigger rejects the cycle + app MergeService.guard_cycle! + resolver MAX_DEPTH=10
T8OAuth Code Injection / Mix-Up (RFC 6819 §4.4.1.1, RFC 8252 §8.1)code interceptionPKCE enforced + downgrade defense (reject public client secret) + redirect URI exact-match + issuer validation + state echo
T9Refresh Token Reuse / Thefttoken theftRT rotation + reuse detection → revoke_chain! revokes the entire subsequent chain. HMAC-SHA256 digest stored. User-row lock
T10RP-to-RP user correlationcross-RP profilingPairwise sub (OIDC Core §8.1): SHA256(sector_id ‖ user_id ‖ app.pairwise_salt)[0..32]. subject_types_supported:["pairwise"]
T11Audit Log Tamperingforged footprintsAuthenticationAuditLog SHA256 hash chain (previous_hash→current_hash) + verify_chain! + GENESIS_HASH. Limit: a DB superuser could recompute the whole chain → exporting to external immutable storage is recommended

STRIDE mapping ​

STRIDEThreats
SpoofingT1, T4, T5, T8
TamperingT7, T11
RepudiationT11
Information DisclosureT10
Denial of ServiceT3
Elevation of PrivilegeT1, T6, T9

Security controls ​

ControlThreats addressedImplementation
PKCE enforced (reject public client downgrade)T8Oauth::AuthorizationsController, Oauth::ClientAuthentication L30-44
Redirect URI exact-matchT8Oauth::RedirectUriValidator
Issuer validationT8discovery issuer ↔ OIDC_ISSUER env
RT rotation + reuse chain revokeT9Oauth::TokensController#rotate_refresh_token L98-101
RT digest (HMAC-SHA256)T9OauthAccessToken#refresh_token_digest
Pairwise subT10Oauth::SubjectIdentifier
Hash chain audit logT11Authentication::AuditLogger
Merge canonical lock + cycle triggerT3, T7MergeService, identity_links_no_cycle_trg, guard_cycle!
9-category credential revoke on mergeT6merge transaction
Webhook HMAC + timestamp + event_idT2RP webhook spec
OTP 5 min + single-use (consumed_at)T2T3 merge OTP
Fresh provider id_token verificationT1, T4SSO callback
Verified email on both providersT5T2 trigger condition

Metrics ​

  • 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=}

A spike in cycle / both_pop_required → escalate to an incident.

Identity가 제품의 신뢰를 만듭니다.