'사람이 승인해야 발행된다'는 규칙에 뚫린 구멍 하나
한눈에 답변
최종 URL은 반드시 사람이 승인해야 발행할 수 있다는 운영 원칙을 세워 뒀는데, 실제 승인 검사 코드는 모델이 slug를 제안했을 때 채워지는 특정 필드만 확인하고 있었다. 결정적인 규칙으로 자동 생성됐거나 사람이 직접 입력한 slug는 그 필드가 비어 있어서, 검사 자체가 통과된 것으로 잘못 처리됐다. 실제로 이 경로로 승인 없이 통과될 뻔한 사례가 있었다.
세워 둔 규칙은 무엇이었나
발행되는 글의 최종 주소(slug)는 반드시 사람이 확인하고 승인해야 한다는 운영 원칙이 있었다. 한번 발행된 URL은 되돌리기 어렵기 때문에, 이 확인 절차를 건너뛰면 안 된다는 것이 이유였다.
실제 코드는 무엇을 확인하고 있었나
승인 여부를 판정하는 함수는 “제안된 slug 필드가 있는데 확정 표시가 없으면 막는다”는 논리로 짜여 있었다. 문제는 그 “제안된 slug” 필드가 모델이 alug를 스스로 제안했을 때만 채워진다는 점이었다. 결정론적인 규칙으로 질의 문장에서 자동으로 만들어진 slug나, 사람이 직접 입력한 slug는 애초에 그 필드 자체가 비어 있었다. 필드가 비어 있으면 이 함수는 “검사할 대상이 없다”고 보고 그냥 통과시켰다.
실제로 무슨 일이 있었나
이 구멍으로 인해, 사람의 최종 승인 없이도 통과될 뻔한 실제 사례가 있었다. 다행히 다른 단계에서 발행까지 이어지지는 않았지만, 규칙이 의도한 대로 작동하지 않고 있었다는 사실 자체가 문제였다.
어떻게 고쳤나
판정 기준을 “확정 표시가 명시적으로 참(true)인가”로 단순화했다. 확정 표시가 없거나, 거짓이거나, 필드 자체가 없는 경우 전부 미승인으로 처리하도록 바꿔서, 어떤 경로로 만들어진 slug든 예외 없이 같은 기준을 적용받게 했다. 이미 발행된 글과 새 slug가 겹치는지 확인하는 절차에도 이미 발행된 글 전체를 비교 대상에 넣어, 후보끼리만 비교하다가 기존 발행 글과 같은 주소를 승인해 버리는 다른 구멍도 함께 막았다.
이 사건에서 얻은 교훈
규칙을 “이런 경우엔 이렇게 판단한다”고 조건별로 나눠 구현하면, 미처 생각하지 못한 경우(이번엔 “필드가 아예 없는 경우”)가 조용히 규칙을 빠져나갈 수 있다. 특히 되돌릴 수 없는 작업 앞의 승인 게이트는, “명시적으로 확인됐다는 증거가 있는가”라는 단일하고 엄격한 기준으로 판정해야 한다 — 증거가 없는 것을 “문제없음”으로 해석하면 안 된다.