비정상 이동 오탐: VPN, 타이밍, 위치 데이터
비정상 이동은 로그인의 IP 기반 위치를 해당 사용자의 이전 저장된 지점과 비교하여, 경과 시간 내에 물리적으로 이동하기 불가능한 속도를 플래그로 표시합니다. high 결과는 단순한 거리/시간 계산이 임계값을 넘었다는 의미입니다 — 그 자체로 계정 탈취의 증거는 아니며, 여러 일반적인 상황에서도 발생할 수 있습니다.
점수 뒤에 있는 계산
POST /v1/risk/check는 사용자의 이전 저장된 위치와 현재 위치 사이의 대권 거리를 계산하고, 이전 관측 이후 경과한 시간으로 나누어 추정 속도를 분류합니다: 시속 500km 미만은 low, 500~900km는 medium, 900km 초과는 high(impossibleTravel: true)입니다. 실제 장거리 비행은 medium/high 경계 근처에 도달할 수 있으며, 이것이 임계값이 단일 기준선이 아니라 범위로 존재하는 이유입니다 — 그러나 계산 자체는 "비행", "VPN", "공유 네트워크" 같은 개념을 전혀 알지 못합니다. 두 지점과 하나의 시간 간격만을 볼 뿐입니다.
첫 번째 관측은 절대 플래그되지 않습니다
사용자의 첫 확인에는 비교할 대상이 없습니다 — 항상 previousLocation이 null이고 distance/speed/elapsed 값도 null인 risk: "low"를 반환하며, (commit: false가 아닌 한) 새로운 저장 기준선이 됩니다. 이는 새 기기나 새 네트워크에서의 첫 로그인이 기본적으로 신뢰를 확립한다는 의미입니다 — 공격자의 첫 행동 역시 동일한 구조적 이유로 플래그되지 않는다는 점을 알아둘 가치가 있습니다.
흔히 오탐을 일으키는 상황
로그인 사이에 VPN 출구 노드나 모바일 네트워크 게이트웨이를 전환하면 실제 이동 없이도 해석된 위치가 먼 거리로 이동할 수 있습니다 — v1에는 VPN이나 데이터센터를 인식하는 억제 기능이 없으므로, 이는 실제 비정상 이동과 동일하게 읽힙니다. 일반적인 모바일 통신사 IP 변동(동일한 물리적 위치가 요청마다 다른 게이트웨이 도시로 해석되는 것)과 공유/애니캐스트 IP 범위도 같은 효과를 낼 수 있습니다. 이 중 어느 것도 공격자를 수반하지 않습니다 — 이는 IP 기반 위치가 작동하는 방식의 부산물이며, 정확도 문서에서 더 자세히 다룹니다.
high 점수가 증명하지 않는 것
risk: "high"는 거리/시간 계산에서 나온 원시 신호일 뿐입니다 — 검증된 계정 탈취 사건도 아니고, 누가 로그인을 시작했는지에 대한 증거도 아니며, 사용자의 자격 증명이 실제로 유출되었는지에 대한 정보도 아닙니다. 이미 보유한 인증 증거와 결합해 자체 심사나 단계별 인증 결정을 위한 하나의 입력값으로 취급하고, 단독으로 행동할 최종 판정으로 취급하지 마세요.
두 관측치 예시
사용자의 저장된 기준선은 09:00 UTC에 관측된 토론토입니다. 로그인이 09:45 UTC에 프랑크푸르트로 해석되는 IP에서 도착합니다 — 45분 만에 약 6,400km로, 시속 900km를 훨씬 넘는 추정 속도이므로 risk: "high"와 impossibleTravel: true를 반환합니다. 이 결과는 다른 대륙에서 탈취된 계정이 사용되고 있는 경우와, 동일한 정당한 사용자의 트래픽이 무관한 이유로 갑자기 프랑크푸르트 VPN 출구를 통해 라우팅된 경우 모두와 일치합니다 — API는 어느 경우든 동일한 분류를 반환하며, 이것이 바로 이 기능이 자동 차단이 아니라 심사를 위한 하나의 입력값으로 설계된 이유입니다.
이 문서가 주장하지 않는 것
이는 Watch-IP 자체의 v1 구현을 설명합니다. 포괄적인 계정 탈취 탐지, 행동 분석, 기기 핑거프린팅을 주장하지 않습니다 — 이들은 이 엔드포인트가 시도하지 않는 다른 기법입니다. 이 엔드포인트의 가용성과 독립적인 요금제군 적용 여부는 확인되지 않았습니다 — 프로덕션에서 의존하기 전에 문의해 주세요.
출처
Watch-IP 편집팀이 작성했습니다(2026년 9월 6일). apps/api/src/lib/risk.ts 및 apps/api/src/durable-objects/session-risk.ts를 기준으로 검토했습니다.