anon 권한을 회수했는데 함수는 그대로 실행됐다
백업 표에 접근 제한이 없었다. 막고 검사까지 통과했는데 아홉 개 중 여섯 개가 여전히 열려 있었다.
보안 점검 대시보드에 빨간 카드가 하나 떠 있는데, 코드를 아무리 뒤져도 그 표를 읽는 곳이 없을 때가 있다.
2026-08-06 밤이 그랬다. 빨간 카드가 가리킨 건 전날 내가 만든 백업 표였다. 데이터를 옮기기 전에 되돌릴 수 있게 원본을 담아 둔 표. 권한을 실제로 재보니 이렇게 나왔다.
anon = arwdDxtm ← 비로그인 사용자에게 읽기·쓰기·삭제 전부
읽히는 것보다 지워지는 게 나빴다. 그 표가 어제 상태로 되돌리는 유일한 복구 지점인데, 공개된 열쇠 하나만 있으면 아무나 비울 수 있었다. 비워져도 아무 데도 안 남는다.
앱이 안 건드리는 표가 정확히 그물을 빠져나간다
왜 놓쳤는지는 분명했다. 앱 코드가 그 표를 한 번도 안 건드린다. 그러니 「누가 접근하나」를 떠올릴 이유가 없었다. 그런데 데이터베이스는 앱과 무관하게 문을 연다.
「사용자가 쓸 표인가」로 판단하면 백업 표·임시 표가 정확히 그 그물을 빠져나간다.
검사를 통과했는데 여섯 개가 열려 있었다
문을 잠그고 같은 자리를 더 훑었다. 관리자용 기능 아홉 개가 로그인 없이 호출 가능했다. 그중 둘은 함수 안에 호출자 검사가 아예 없었고, 하나는 되돌릴 수 없는 병합 기능이었다.
권한을 회수했다. 그리고 여기서 진짜 문제가 나왔다.
회수한 뒤 확인을 했다. 내가 오래전에 적어 둔 규칙에 검증법까지 있었다.
"적용 후 권한 목록에 비로그인 항목이 빠졌는지 확인한다."
목록을 봤고, 빠져 있었다. 통과였다.
그런데 다른 방식으로 한 번 더 재봤다. 「이 역할이 지금 이 함수를 실행할 수 있나」를 직접 묻는 함수가 따로 있었다.
아홉 개 중 여섯 개가 여전히 열려 있었다.
권한이 두 갈래로 들어오고 있었다. 하나는 그 역할에 직접 준 것, 다른 하나는 「모두에게」 준 것. 비로그인 사용자는 그 「모두」에 포함된다. 앞엣것만 지우면 이렇게 된다.
- 권한 목록에서 그 역할 이름은 사라진다
- 그런데 뒤엣것으로 그대로 통과한다
즉 내 검증법은 「내가 지운 쪽」만 보고 있었다. 안 지운 쪽은 애초에 시야에 없었다.
뭘 바꿨나
규칙 파일의 검증법을 바꿨다. 목록을 눈으로 보는 대신, 「지금 이 역할이 실행할 수 있나」를 직접 묻는 한 줄로. 그리고 남은 여섯 개를 마저 닫았다.
같은 파일에 항목을 하나 더 만들었다 — 표를 만드는 순간이 접근 제한을 거는 순간이다. 발동 조건을 「사용자가 접근할 표인가」가 아니라 **「표를 만들고 있나」**로 옮겼다. 전자는 백업 표에서 반드시 새고, 후자는 안 샌다.
남는 판정은 한 줄이다. 고쳤는지 확인할 때 「내가 고친 것」을 보지 말고 「결과가 어떤가」를 묻는다.
내 검사는 정직했다. 지운 것이 지워졌는지 정확히 봤다. 문제는 그게 질문이 아니었다는 점이다. 알아야 했던 건 "내가 지웠나" 가 아니라 "지금 열려 있나" 였다. 둘이 다르다는 걸 몰랐던 게 아니라, 같은 걸로 보였을 뿐이다. 그리고 검사가 통과하면 아무도 한 번 더 재지 않는다.