비밀번호를 채팅에 흘렸다 — 그 후 30분 룰
결제·정산 자동화 팀에서 자격증명 유출은 단순 보안 사고가 아니다. HEDVION이 실제 경험 끝에 만든 30분 룰의 배경, 4단계 대응 절차, 그리고 지금 당장 팀에 도입할 수 있는 실행 체크리스트.
결제·정산 인프라에서 자격증명 유출이 다른 이유
일반적인 웹 서비스에서 DB 비밀번호가 채팅에 노출됐다면 최악의 경우는 데이터 유출이다. 불행한 일이지만 범위는 그 데이터베이스 안에서 끝난다. 결제·정산 자동화를 운영하는 팀에서 같은 사고가 나면 피해 범위가 다르게 펼쳐진다. PG사 API 키가 노출되면 실시간으로 위변조 거래가 발생할 수 있다. 정산 스크립트에 쓰이는 내부 서비스 계정이 유출되면 지급 큐가 조작될 수 있다. 계좌 이체 자동화에 쓰이는 은행 API 토큰은 그 자체로 금전과 직결된다.
HEDVION은 결제 승인·정산 자동화·세금계산서 발행을 단일 파이프라인으로 운영한다. 팀 규모가 작다는 건 민첩하다는 뜻이기도 하지만, 동시에 한 사람이 PG 연동부터 정산 배치까지 모두 건드린다는 뜻이기도 하다. 슬랙 채널 하나에서 개발 논의와 운영 알림이 섞인다. 이 환경에서 "터미널 명령어 복붙"이라는 흔한 실수는 단순 실수가 아니라 금전 리스크로 직결된다. 30분 룰은 그 현실에서 출발한다.
왜 30분인가 — 시간이 지날수록 커지는 노출 반경
"발견하면 즉시 처리한다"는 원칙은 맞다. 하지만 팀 공유 규칙으로 쓰기엔 너무 모호하다. "즉시"가 어떤 사람에게는 5분이고, 어떤 사람에게는 "지금 하던 PR 마무리하고"가 된다. 우리가 내부적으로 추적한 결과, 자격증명 유출 사고에서 악용이 시작되는 중간값 시간은 업계 사고 사례 기준 약 20~40분이다. 2022년 GitGuardian 보고서에 따르면 공개된 시크릿이 처음 접근되는 중간 시간은 14분이었고, Slack이나 Teams 같은 협업 채널은 실시간 검색이 가능해 이 시간이 더 짧다.
30분은 그 현실을 팀이 기억하기 쉬운 숫자로 압축한 것이다. 실제로 우리 대응 절차를 수행하면 — 유출 확인, 자격증명 무효화, 의존 서비스 교체, 팀 공유 — 충분히 수행 가능한 시간이다. 반면 "나중에 처리하자"는 판단이 개입하면 이 30분이 며칠이 된다는 걸 우리는 직접 경험했다. 2024년 초, 내부 정산 배치 스크립트의 DB 연결 문자열이 슬랙 DM에 잘못 붙여넣어진 적이 있었다. 발견은 당일에 했지만, "어차피 DM이고 상대방이 팀원이니까"라는 판단이 실제 무효화를 3일 뒤로 밀었다. 그 3일 사이에 해당 계정으로 비정상 접근 시도 로그가 찍혔다. 실제 피해는 없었지만, 우리는 왜 그 로그가 생겼는지 지금도 설명할 수 없다.
HEDVION의 실제 대응 절차: 4단계 + 검증
우리가 지금 쓰는 절차를 공개한다. 단계별 순서가 중요하다. 특히 1단계와 2단계의 순서를 바꾸면 안 된다.
1단계 — 무효화 먼저 (0~10분) 노출된 자격증명을 지금 즉시 비활성화한다. 채팅 메시지 삭제나 로그 정리는 그 다음이다. 비밀번호면 바로 변경, API 키면 폐기 후 신규 발급, 세션 토큰이면 강제 만료 처리. 많은 팀이 "우선 메시지 지우고"를 먼저 하는데, 그 시간에 이미 누군가가 캐시했을 수 있다. 무효화가 실행됐다면 메시지가 남아있어도 위험이 급감한다.
PG사 API 키의 경우, 우리는 각 PG사별로 키 폐기 URL을 사내 Notion 런북에 저장해 두고 있다. 이니시스, 토스페이먼츠, 카카오페이 모두 관리 콘솔 내 즉시 폐기 기능이 있다. 런북이 없으면 이 단계에서 "콘솔 어디서 들어가지?"를 찾는 데 5~10분이 소요된다.
2단계 — 노출 범위 파악 (10~20분) 어디에서 유출됐고, 누구에게 보였을 가능성이 있는지를 정리한다. 슬랙 채널이라면 채널 멤버 수와 외부 게스트 여부, 로그 저장소라면 해당 로그의 접근 권한과 보존 기간. 우리 서비스는 정산 배치 로그가 S3에 적재되는데, 버킷 접근 정책이 느슨하게 설정된 시기에 쿼리 파라미터로 토큰이 찍힌 적이 있었다. 이때 S3 액세스 로그를 확인하지 않으면 외부 접근 여부를 영원히 알 수 없다.
3단계 — 의존 서비스 교체 (15~25분, 병렬 가능) 변경된 자격증명을 사용하는 모든 곳을 업데이트한다. 이 단계를 빠뜨리면 서비스가 중단된다. 우리의 경우 GitHub Actions Secret, Kubernetes Secret, 배치 서버 환경 변수, Notion 런북 내 테스트 계정 섹션까지 총 4곳을 체크리스트로 관리한다. 이 목록을 미리 만들어 두지 않으면 배포 직후에 "아, 거기도 있었지"가 반드시 나온다.
4단계 — 팀 공유 및 기록 (25~30분) 무슨 일이 있었고, 어떻게 처리했으며, 재발 방지를 위해 무엇을 바꾸는지를 채널에 공유한다. 처벌 문화가 되면 유출 사실 자체를 숨기게 된다. 우리는 #incident 채널에 5줄 템플릿으로 기록하고, 이것이 쌓여서 분기 회고 자료가 된다.
+검증 단계 (30분 이후) 무효화된 자격증명으로 실제로 접근이 불가능한지 테스트한다. 신규 발급된 키로 smoke test를 돌려 서비스가 정상 동작하는지 확인한다. 이 단계를 생략하면 "처리했다"는 착각 속에 서비스가 죽어있는 경우가 생긴다.
트레이드오프: 속도 vs. 정확성
30분 룰은 "정확한 조사보다 빠른 무효화 우선"이라는 트레이드오프를 명시적으로 선택한다. 가끔은 섣부른 무효화가 비용을 만든다. PG사 API 키를 폐기하면 해당 키로 실행 중인 정기결제 스케줄러가 즉시 실패한다. 우리가 실제로 겪은 사례에서, 월요일 오전 배치가 돌아가는 도중에 키를 폐기했더니 당일 정산 배치 42건이 에러 처리됐고 수동 재처리에 2시간이 걸렸다.
이 경험에서 우리가 추가한 항목이 있다. 무효화 전에 "현재 진행 중인 배치가 있는가"를 30초 안에 확인하는 것이다. 배치 서버 로그 한 줄이면 충분하다. 진행 중이라면 배치가 끝나는 시점(대부분 5분 이내)을 기다린 뒤 무효화한다. 이 30초 확인이 2시간 재처리를 막는다. 규칙은 단순해야 하지만, 단순함이 맹목적 실행이 되어선 안 된다.
예방: 사후 처리보다 비용이 낮은 구조적 변경
사고 이후 우리 팀이 실제로 바꾼 것들을 구체적으로 나열한다.
첫째, 연결 문자열 대신 환경 변수 이름을 공유하는 문화다. "DB가 연결이 안 돼요"라는 메시지에 "DATABASE_URL 값 다시 확인해봐요"가 아니라 "DATABASE_URL 환경 변수 제대로 설정됐는지 봐요"로 답하는 것이다. 실제 값은 채팅에 등장하지 않는다.
둘째, 터미널 히스토리 보안 설정이다. HISTCONTROL=ignorespace 설정을 팀 온보딩 문서에 추가했다. 명령어 앞에 스페이스를 붙이면 히스토리에 남지 않는다. 비밀번호가 포함된 psql 명령어는 스페이스로 시작하는 것을 관례화했다.
셋째, 운영 환경 로그의 쿼리 파라미터 마스킹이다. Node.js 기반 정산 서버에서 axios 인터셉터 레벨에서 Authorization 헤더와 토큰 파라미터를 [REDACTED]로 치환한다. 코드 10줄이 로그 검색 실수를 구조적으로 막는다.
넷째, 슬랙 워크플로우 알림에서 실제 값 대신 마스킹된 형태(앞 4자리 + ***)로 표시하도록 정산 알림 템플릿을 수정했다. 정산 완료 알림에 계좌번호 앞뒤를 마스킹하는 것은 3시간 작업이었지만, 이후 해당 채널에서 실수로 원본 값이 노출된 적이 없다.
지금 바로 써먹을 실행 체크리스트
아래는 추상적인 원칙이 아니라 내일 당장 팀에서 쓸 수 있는 항목이다. 사고가 나기 전에 준비해야 효과가 있다.
사전 준비 (지금 당장)
- PG사별 API 키 폐기 URL을 팀 런북에 정리한다. 이니시스·토스페이먼츠·카카오페이 각각 어디서 폐기하는지 3분 안에 접근 가능해야 한다.
- 자격증명을 사용하는 서비스 목록을 작성한다: GitHub Actions Secret, K8s Secret, 배치 서버 env, 외부 연동 설정. 이 목록이 3단계의 체크리스트가 된다.
#incident채널 또는 동등한 채널을 만들고, 5줄 사고 기록 템플릿(발생 시각 / 유출 내용 / 무효화 완료 시각 / 노출 범위 / 재발 방지 조치)을 핀 메시지로 고정한다.
사고 발생 시 (30분 안에 완료)
- 배치 진행 여부 30초 확인 → 무효화 실행
- 신규 자격증명 발급
- 노출 채널/저장소 접근 로그 확인
- 의존 서비스 4곳(또는 팀 목록 기준) 업데이트
- 신규 키로 smoke test
-
#incident에 5줄 기록
사후 개선 (이번 주 안에)
- 운영 로그에서 Authorization 헤더·토큰 파라미터 마스킹 적용 여부 확인
- 슬랙 알림 템플릿에 실제 계좌·키 값이 노출되는 항목 파악 및 마스킹 처리
- 팀 온보딩 문서에
HISTCONTROL=ignorespace추가
실수가 없는 팀은 없다. 하지만 실수가 났을 때 30분 안에 막을 수 있는 팀과, 3일 뒤에 "어, 그거 처리했어요?"라고 묻는 팀의 차이는 시스템에 있다. 그 시스템을 만드는 비용은 지금 이 글을 읽는 시간보다 크지 않다.
— by slecs, HEDVION
* 위 링크는 인프런 affiliate 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
* 위 추천 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.