은하계 커뮤니티 연대기 (Claude Code)

회원가입을 닫아뒀다고 3일간 믿었다

Kenneth Kang · 자유 · 2026.07.24

회원가입을 닫아뒀다고 3일간 믿었다

혼자 만든 커뮤니티 서비스를 출시하고 며칠 뒤, 나는 회원가입을 다시 닫았다.

이유는 단순했다. 실서비스에 올려놓고 보니 미진한 게 눈에 들어왔다. 약관은 초안 상태였고, 회원 탈퇴 기능이 아예 없었고, 이메일 인증도 없었다. 사람이 들어오기 전에 채워야 할 것들이었다.

그래서 가입을 닫고 하나씩 메꿨다. 약관을 실제 구현과 맞추고, 탈퇴를 익명화 방식으로 설계하고, 이메일 인증을 붙이고, 비공개 글 필터를 손봤다. 마음이 편했다. 가입이 닫혀 있으니 아무도 못 들어온다는 전제 위에서 차분히 작업했다.

그 전제가 3일 동안 틀려 있었다.

발견

관리자 페이지에 "가입 On/Off 토글"을 만들려던 참이었다. 지금은 코드 상수를 고치고 재배포해야 여닫을 수 있는데, 문제가 생겼을 때 몇 분씩 뚫려 있는 게 싫었다. 관리자 화면에서 즉시 끄고 싶었다.

작업을 시작하기 전에 현재 상태를 확인해달라고 했다. 답이 이랬다.

SIGNUP_OPEN프론트엔드 상수뿐입니다. 백엔드 POST /auth/signup과 구글 신규가입에는 아무 검사가 없습니다.

내가 닫았다고 믿었던 건 web/lib/api.ts의 상수 한 줄이었다.

const SIGNUP_OPEN = false;

이건 로그인 화면에서 가입 탭을 숨기는 코드였다. 그게 하는 일의 전부였다. API는 처음부터 끝까지 열려 있었다. curl로 요청 한 번 보내면 계정이 만들어졌다. 구글 OAuth 신규가입도 마찬가지였다.

그리고 하필 그 며칠 전, 나는 Cloudflare의 봇 검사를 /api/ 경로 전체에서 껐다. 내가 만든 MCP 서버가 그 검사에 걸려 403을 맞았기 때문이다. 즉 가입 엔드포인트는 봇 검사도 없이 열려 있었다.

실제로 사고가 나지 않은 건 두 가지 덕이었다. 아무도 이 서비스를 몰랐고, 시간당 5회 rate limit이 걸려 있었다. 운이었다.

왜 놓쳤나

이 상수를 쓴 건 나다. 몇 주 전에, 내 손으로.

그런데 시간이 지나면서 SIGNUP_OPEN = false는 머릿속에서 **"가입이 닫혀 있다"**는 사실로 굳었다. 그게 프론트엔드 파일에 있다는 것, 서버는 그 존재를 모른다는 것 — 알고 있었는데도 떠오르지 않았다. 상태를 확인하는 대신 기억을 참조했다.

같은 시기에 비슷한 걸 두 개 더 발견했다.

하나. 개별 글 상세 API에 비공개 게이트가 없었다. 내 기록에는 "게이트 적용 완료"라고 남아 있었지만, 실제로 막혀 있던 건 공유용 페이지 하나뿐이었다. 글 ID만 알면 남의 비공개 글 본문이 통째로 열렸다. 목록 필터만 손보고 넘어갔다면 그대로 남았을 것이다.

둘. 사이트에 공개된 이용약관 페이지가 옛 버전이었다. 나는 약관 문서를 고쳤고, 페이지가 그 문서를 읽어 렌더한다고 생각했다. 실제로는 페이지 안에 문자열이 하드코딩된 사본이었다. "탈퇴 시 개인정보를 지체 없이 파기"라고 적힌, 구현과 다른 법적 문서가 공개돼 있었다.

세 개의 공통점은 명확하다. 내 기록과 기억은 맞았고, 코드는 달랐다.

고친 방법

가입 상태를 DB(app_settings)로 옮기고, 백엔드 두 생성 지점에서 강제했다. 이메일 가입과 구글 가입, 양쪽 다. 닫혀 있으면 403이다.

프론트엔드는 하드코딩을 버리고 GET /auth/signup-open을 읽는다. 이제 프론트는 상태를 표시만 하고, 판단은 서버가 한다.

기본값은 닫힘으로 했다. 설정이 누락되거나 조회에 실패하면 열리는 게 아니라 닫히는 쪽으로. 실수했을 때 안전한 방향으로 실패해야 한다.

그리고 테스트를 하나 추가했다. 게이트를 일부러 무력화하면 실패하는 테스트다. 나중에 누가(나 포함) 이걸 되돌리면 CI가 잡는다.

검증은 이렇게 했다.

curl -X POST https://.../api/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]", ...}'
# → 403

화면에서 확인하는 게 아니라 API를 직접 찔러서. 애초에 화면만 보고 있었던 게 문제였으니까.

남은 것

교훈이랄 게 두 개 있다.

첫째는 뻔하다. 프론트엔드 플래그는 보안이 아니다. 알고 있었고, 내가 쓴 코드인데도 잊었다.

둘째가 더 오래 남았다. 기록은 그때의 의도를 남기지, 지금의 코드를 보증하지 않는다. 나는 결정 로그를 성실히 써왔고 그게 도움이 됐다. 다만 "적용 완료"라고 쓰인 항목을 다시 확인하지 않는 근거로 썼던 게 문제였다.

이제는 보안이나 프라이버시에 걸리는 항목은, 기록에 뭐라고 쓰여 있든 코드를 다시 읽는다. 확인 비용이 30초고 놓친 비용은 사고니까.

가입은 지금 다시 열려 있다. 이번에는 서버가 알고 있다.


혼자 만드는 관심사 커뮤니티 galaxyconstellate.com을 만들며 겪은 일들을 기록하고 있습니다.

은하계에서 보기나도 은하계 만들기