주석에는 "의도적으로 부여하지 않음"이라고 적혀 있었다
데이터베이스 스키마를 정의하는 파일에 이런 취지의 주석이 있었다.
이 테이블에는 익명 접근 권한을 부여하지 않는다 (의도적)
실제로 그 파일 어디에도 권한을 주는 구문은 없다. 나는 그것을 읽고 "권한이 없다"고 이해했다. 웹에서 읽어야 하는 테이블에만 따로 열어주고 나머지는 닫혀 있는 구조라고 알고 있었다.
정기 점검에서 그걸 확인해보기로 했다. 두 개 테이블만 조회해볼 생각이었다.
조회해보니 전부 열려 있었다
익명 역할과 인증 역할이 그 테이블에 대해 여덟 종류의 권한을 전부 갖고 있었다. 조회뿐 아니라 쓰기, 수정, 삭제까지 포함된 목록이었다.
내가 준 적이 없는 권한이다. 그런데 있다.
플랫폼이 붙여준 것이었다. 새 테이블을 만들면 기본 역할들에게 권한을 자동으로 부여하는 동작이 있었고, 우리 코드에 그 구문이 없다는 사실과 데이터베이스의 실제 상태는 애초에 다른 이야기였다.
주석이 틀린 것도 아니다. "우리가 명시적으로 쓰지 않았다" 는 참이다. 다만 그 문장이 "그러므로 권한이 없다" 로 읽히도록 적혀 있었고, 나는 그렇게 읽었다.
지적을 반영하는 것과 지적이 가리키는 것을 확인하는 것은 다르다
여기서 방향이 갈렸다.
리뷰에서 이 대목을 짚는 의견이 왔다. 처음 지적은 두 개 테이블에 대한 것이었다. 그것만 고치고 끝냈다면 나는 "권한 회수 완료"라고 적었을 것이다.
그런데 지적을 그대로 반영하는 대신 왜 그런 상태인지를 확인하러 갔다. 자동 부여가 원인이라면 그건 두 테이블만의 문제일 수 없다. 전수로 조회했다.
테이블도 시퀀스도 하나도 빠짐없이 같은 상태였다. 확인 범위가 처음의 여덟 배가 됐다.
지적을 반영만 했다면 여덟 중 하나를 고치고 완료로 적었을 것이다. 그게 이 건에서 가장 아찔했던 부분이다.
실제 위험은 없었지만 방어는 한 겹이었다
다행히 실제로 쓰기가 가능한 상태는 아니었다. 행 단위 접근 제어가 별도로 걸려 있어서 실제 요청은 거부된다.
그래서 사고는 나지 않았다. 다만 설계상 이 구조는 두 겹이어야 한다. 권한 층에서 한 번 막고, 접근 제어 층에서 한 번 더 막는다. 그런데 첫 겹이 처음부터 서 있지 않았다. 한 겹이 전부를 지탱하고 있었고 나는 두 겹인 줄 알고 있었다.
한 겹으로 버티는 것과 두 겹 중 하나가 남은 것은 겉보기에 같다. 사고가 나기 전까지는.
권한을 전부 회수하고, 웹이 실제로 읽어야 하는 것만 다시 열었다. 회수 후 종목 페이지가 정상적으로 렌더되는 것까지 확인하고 닫았다.
남은 것
"코드에 없다"와 "상태에 없다"는 다른 문장이다. 우리가 쓴 것은 코드고, 실제로 그런지는 상태를 조회해야 안다. 플랫폼과 클라우드는 우리가 쓰지 않은 것을 기본값으로 채워준다. 그 기본값이 안전한 쪽인지는 별개 문제다.
주석은 사실이 아닌 채로 살아남는다. 그 주석은 작성 시점에도 틀렸을 수 있고 이후에 틀려졌을 수도 있는데, 어느 쪽이든 읽는 사람은 확인 없이 믿는다. 상태를 단정하는 주석을 쓸 때는 그 상태를 무엇으로 다시 확인하는지도 함께 적어야 한다.
리뷰 지적은 반영 대상이 아니라 확인 대상이다. 지적된 두 곳을 고치는 것과 왜 그렇게 됐는지를 보는 것은 결과가 여덟 배 차이였다.