사이드프로젝트 slecs

파트너 정산 은행 도메인 누락으로 송금 인증 실패하던 문제 수정

목차

파트너 정산 채널을 운영하다 보면 예상치 못한 방식으로 데이터가 들어온다. 원래 설계 의도는 "송금 완료 캡처 이미지를 첨부"였는데, 어느 시점부터 메시지 앱에서 공유된 링크를 그대로 채널에 붙여넣는 케이스가 자꾸 늘어났음. 사용자 입장에서는 같은 행동인데, 시스템 입장에서는 전혀 다른 파싱이 필요한 흐름이다.

그 흐름을 처리하는 로직이 이미 있었다. 메시지 본문에서 URL을 뽑고, 도메인을 추출하고, 화이트리스트에 있는 은행 도메인이면 송금 인증 성공으로 처리하는 방식. 처음엔 잘 돌아갔는데, CS팀에서 "특정 은행 링크를 붙여넣으면 매번 인증 실패가 난다"는 리포트가 연달아 들어왔음. 한 건이면 사용자 실수로 볼 수 있는데, 같은 증상이 다른 파트너에게서도 반복되면 코드 문제다.

URL 파서 자체에 버그가 있나 싶어 뜯어봤는데, 파서는 멀쩡했다. 문제는 더 아래 단계, 뽑아낸 도메인을 검증하는 화이트리스트였음.

왜 정규식이 아니라 화이트리스트였나

이 로직을 처음 설계할 때 "느슨한 정규식 vs 명시적 화이트리스트" 선택지가 있었고, 화이트리스트를 골랐다. 이유는 도메인 특성에 있음.

정규식으로 은행 도메인을 매칭하려면 결국 두 갈래다. 하나는 .co.kr 같은 TLD만 허용하는 느슨한 패턴, 다른 하나는 알려진 은행 도메인 구조를 반영한 빡빡한 패턴. 느슨한 쪽은 피싱 도메인이 통과할 위험이 있고, 빡빡한 쪽은 결국 화이트리스트랑 관리 복잡도가 비슷해지거나 더 높아진다. 금융 맥락에서 오탐(false positive) - 엉뚱한 URL을 정상으로 통과시키는 것 - 의 비용은 미탐보다 훨씬 크다.

방식 장점 단점
느슨한 정규식 신규 도메인 수동 추가 불필요 피싱·유사 도메인 통과 위험
엄격한 정규식 어느 정도 안전 패턴 관리 복잡도가 화이트리스트와 비슷하거나 이상
명시적 화이트리스트 가장 안전, 의도 명확 외부 세계 변화마다 코드 수정 필요

이번에도 이 판단 자체는 바꾸지 않았다. 화이트리스트 유지. 다만 리스트에 구멍이 나 있었던 것.

초기 구현 시 주요 시중은행 6개만 넣고 굳어진 코드였는데, 그사이 금융업 지형이 꽤 달라졌음. 인터넷전문은행 2곳, 지방은행 2곳, 외은 지점 1곳, 증권사 종합계좌 서비스 1곳이 파트너 정산 채널에서 실제로 쓰이고 있었는데 리스트엔 없었다. 딱 6개가 빠진 것.

핫픽스는 진짜 한 줄이었고, 그다음이 일이었음

# before
ALLOWED_BANK_DOMAINS = {
    "bank-a.co.kr",
    "bank-b.com",
    # ... 4개 더
}

# after
ALLOWED_BANK_DOMAINS = {
    "bank-a.co.kr",
    "bank-b.com",
    # ... 4개 더
    "d1.co.kr", "d2.com", "d3.co.kr",   # 인터넷전문은행 2개, 지방은행 1개
    "d4.co.kr", "d5.co.kr", "d6.com",   # 지방은행 1개, 외은지점, 증권사
}

PR diff 몇 줄. 누락 도메인 6개를 테스트 케이스에 추가하고, 통과 확인하고, 머지. 배포까지 몇 시간 안 걸렸음. 수정 난이도 자체는 거의 0이었다.

근데 수정이 쉽다는 게 오히려 더 찜찜했다. 다음에 새 파트너가 신규 인터넷전문은행이나 온라인 전용 증권사를 쓰면, 같은 일이 또 생긴다. 그때도 CS 리포트로 알게 되는 구조라면 매번 늦는 것이고, 이 패턴은 계속 반복된다.

더 본질적인 문제는 "왜 CS 리포트 전까지 우리가 몰랐냐"였음. 도메인 매칭 실패가 예외로 터지지 않고 그냥 "인증 불가" 처리로 조용히 흘러갔기 때문이다. 에러 로그에 아무것도 남지 않음. 인증 실패율이 올라가도 뭣 때문인지 드릴다운할 수단이 없었다. 이 구조에서는 문제가 생겨도 CS가 알려줄 때까지 시스템 쪽에서는 감지가 안 된다.

그래서 핫픽스 직후에 두 개를 추가 티켓으로 끊었음.

  • 도메인 매칭 실패 메트릭 추가: 화이트리스트 미히트 케이스를 카운터로 발행하고, 임계치 넘으면 알람. 이게 있었다면 CS 리포트보다 먼저 잡혔을 거다.
  • 은행 코드-도메인 분리 구조 설계: 은행 코드 테이블에 신규 코드가 등록될 때 대응 도메인이 비어있으면 자동으로 담당자 알람이 가는 구조. 화이트리스트를 유지하되, 리스트가 낡아지는 걸 시스템이 먼저 감지하게 하는 것.

두 번째는 당장 치지 않았고 다음 스프린트 백로그에 올라가 있다. 이번엔 빠른 패치가 먼저였으니까.

외부 세계 데이터를 코드에 박는다는 것

이번 건에서 다시 확인한 패턴이 있다. 외부 기관 목록, 국가/통화/은행 코드, API 엔드포인트 세트 같은 건 코드에 상수로 박아두는 순간부터 부패 시계가 돌아간다. 처음엔 맞는 값이지만 시간이 지날수록 틀릴 확률이 올라가고, 언제 틀려지는지 코드는 모른다. 외부 세계가 코드보다 빠르게 변하는 영역에서 특히 그렇다.

이런 류 상수를 관리할 때 생각해볼 접근들.

  • 갱신 주기를 코드 옆에 남기기: 주석이든 문서든, "이 리스트는 언제 기준"이라는 메타 정보가 있으면 다음 사람이 "이거 최신이냐"를 먼저 물어볼 수 있다. 없으면 기본값은 "맞겠지"다. 그 기본값이 나쁜 기본값이다.
  • 실패를 가시화하기: 매칭 실패가 조용히 흘러가지 않도록 메트릭으로 노출. 알람까지 붙이면 CS보다 시스템이 먼저 알 수 있음. 이게 이번에 가장 빠르게 체감한 부재였다.
  • 외부 소스 연동 검토: 공식 기관이 표준 코드 목록을 제공하는 경우, 직접 관리하는 대신 주기적으로 pull하는 구조를 고려할 수 있다. 항상 가능하진 않지만 선택지로는 검토해볼 만함.

화이트리스트를 버리자는 게 아니라, 화이트리스트를 운영 가능한 형태로 관리하자는 것. 리스트가 살아있으려면 리스트가 낡아지고 있다는 걸 시스템 스스로 감지할 수 있어야 한다.

핫픽스 자체는 몇 시간짜리 작업이었는데, 거기서 파생된 "어떻게 하면 다음 번에 더 빨리 알 수 있냐"는 질문이 더 오래 걸렸다. 매칭 실패 카운터 하나를 메트릭으로 발행하는 건 PR 한 장짜리지만, 이게 운영 가시권을 완전히 바꿔놓는다. 조용히 실패하는 코드는 찾기 전까지 존재하지 않는 것처럼 보인다. 그게 제일 위험한 종류의 버그다.

댓글 0

첫 댓글 달아줘.