뒷수습과 재제출, 그 사이 어딘가에서 보안 구멍 두 개를 막은 날
목차
오늘 하루를 한 단어로 정리하면 "봉합"이다. 새 기능을 터뜨린 날이 아니라, 금이 간 곳을 메우고, 흘러내리는 것을 잡고, 잘못 적힌 기록을 바로잡는 데 대부분을 썼다. 커밋 수는 많은데 뿌듯하다기보다 안도감이 앞서는 날이 있다. 오늘이 딱 그랬다.
아침부터 보안 구멍 두 개
첫 커밋이 fix로 시작하면 그날은 이미 긴장 모드다. 회원가입 이메일 인증 우회 차단 건이 오전 일찍 올라왔는데, 이게 생각보다 심각한 조합이었다. emailRequired=N으로 설정된 상태에서 인증 필수 옵션이 동시에 켜져 있을 때 검증 로직이 통째로 건너뛰어지는 케이스였다. 두 조건이 충돌할 것 같으면 당연히 더 엄격한 쪽이 이겨야 하는데, 코드는 그냥 emailRequired=N을 보고 인증 검사를 스킵해버렸다.
egovframework 기반 레거시 구조라 조건 분기가 여러 계층에 흩어져 있어서 어디서 뚫리는지 찾는 것 자체가 번거로웠다. 실제로 이 조합으로 가입 시도를 했을 때 인증 없이 계정이 만들어지는 게 확인됐고, 그 시점부터 그냥 빠르게 패치하고 배포하는 쪽으로 방향을 잡았다. 재현 조건이 명확했으니 픽스 자체는 어렵지 않았지만, "왜 이 조합이 테스트에서 안 걸렸지"라는 생각은 내내 걸렸다. 조건의 교집합을 테스트하지 않고 각 케이스만 단독으로 검증했던 거다. 반성.
두 번째 보안 픽스는 스케줄러 분산락이었다. fail-open 상태라는 걸 알면서도 미뤄뒀던 건데, 오늘 결국 fail-closed로 전환했다. fail-open은 락 획득에 실패해도 작업을 그냥 진행하는 방식이다. 개발 초기엔 "락 서버 내려갔다고 스케줄러 전체 멈추는 게 더 나쁘다"는 논리로 열어뒀는데, 시간이 지나고 보니 그 판단이 틀렸다. 중복 실행이 발생할 수 있는 상황이 안전 쪽으로 기우는 게 맞다. 거기에 락 소유자 UUID 검증도 붙였다. 어떤 인스턴스가 락을 들고 있는지 확인하지 않으면 여러 노드가 동시에 "내가 락 주인"이라고 착각할 수 있다. 두 가지를 한 커밋에 묶은 건 서로 맞물려 있어서였다. fail-closed로 바꾸면서 UUID 검증 없이 가면 엉뚱한 노드가 락을 해제하는 경우가 생길 수 있으니.
3일 전 사고의 그림자
query-tuning.md에 DML 실행 룰을 추가했다. 정확히는 렌더 하네스에서 DML을 실행할 때 namespace.id를 필수로 명시해야 한다는 룰이다. 8월 20일에 개발 DB에서 사고가 있었고, 오늘 그 재발방지 문서를 커밋했다. 사고 자체의 디테일은 커밋에 없으니 여기 풀지 않겠지만, 나흘 지나서 룰이 추가됐다는 건 그 사이에 원인 파악과 내부 공유가 있었다는 뜻이다.
이런 룰은 쓰는 것 자체보다 지켜지는 게 어렵다. .claude/rules/ 아래 두면 AI 하네스가 참조하긴 하는데, 사람이 직접 쿼리 날릴 때 이 파일을 열어보는 습관이 팀에 있느냐가 실질적인 효과를 가른다. 룰 파일 추가가 진짜 방어막이 되려면 파이프라인 단에서 강제하는 검증이 있어야 하는데, 그건 다음 과제로 남겼다.
AGENTS.md도 오늘 새로 만들었다. 브라우저 자동화는 agent-browser CLI를 통하라는 룰 문서다. 이것도 "해보니까 이게 맞더라"는 결론이 나온 다음 표준화하는 패턴이다. 실행은 이미 이렇게 하고 있었는데 문서가 없었던 것.
Loki 포트 하나 여는 데 왜 이렇게 오래 걸렸냐면
docker-compose.prod.yml에서 Loki 3100 포트를 노출했다. WAS2랑 쇼핑몰 플랫폼에서 Promtail이 로그를 쌓아올릴 수 있도록 열어준 것인데, 작업 자체는 한 줄 수준이었다. 근데 이걸 오늘 하게 된 건 다른 모니터링 연결 작업이 막혀 있어서였을 가능성이 높다. 포트 안 열려 있으면 Promtail이 연결을 못 만들고, 그걸 확인하는 과정에서 여기까지 왔겠지. 짧은 커밋 뒤에는 보통 길고 지루한 디버깅 시간이 숨어 있다.
스샷 v2 - 에이전시 스타일 전면 교체
오늘 작업량에서 가장 덩어리가 큰 건 아마 이쪽이다. prayertimes 앱 스토어 스크린샷을 v2 에이전시 스타일로 전면 교체했다. 커밋 메시지에 "전면 교체"라고 써놨는데, 실제로는 새 스타일 정의 → 하네스 구현 → 골든 이미지 생성 → 검토 → 수정 → ASC/Play 업로드까지 이어지는 파이프라인이다.
lib/screenshots_harness.dart랑 store/asc_screenshots_v2.py가 수정됐고, 키블라 화면 샷이 신규로 들어갔다. lib/screens/qibla_screen.dart도 건드렸으니 실제 UI 수정도 있었던 것으로 보인다. iOS는 ios/Podfile과 Podfile.lock까지 변경 흔적이 있는데, 스크린샷 렌더링 과정에서 의존성 뭔가가 맞지 않아서 건드렸거나, 아니면 Podfile은 다른 이유로 갱신된 거겠지.
에이전시 스타일이라고 부르는 건 이번에 prayertimes에서 정립한 마케팅 프레임이다. 제품 UI를 그냥 찍는 게 아니라 배경·카피·구도를 잡아서 "앱스토어 상세 페이지를 보는 사람이 클릭하고 싶게" 만드는 방식. 오늘 mobile-app-launch.md에 이 프레임 v2 표준을 등재했는데, 거기에 두 가지 함정도 같이 기록했다. NO_CAS_MEDIATION과 iPad 상태바 문제. 아마 스크린샷 생성 과정에서 직접 밟아본 함정들일 거다.
NO_CAS_MEDIATION은 광고 미디에이션 관련 설정 같은데, 이게 켜진 상태에서 스크린샷을 찍으면 UI가 달라지거나 에러가 뜨는 상황이 생겼던 것 같다. iPad 상태바는 캡처 시 시뮬레이터 기기 종류에 따라 상태바 높이가 달라져서 레이아웃이 틀어지는 문제가 있다. 두 가지 다 "한 번 걸리면 처음엔 원인을 못 찾는" 종류의 문제다. 기록 잘 해뒀다.
독일어 스크린샷도 갱신됐다. shot_de_2_why, shot_de_3_upcoming, shot_de_4_car, shot_de_4_data 네 장. 리스팅 픽스 커밋에서 "free-wording out, real modal look"이라고 했는데, 광고성 문구를 빼고 실제 앱의 모달 UI처럼 보이게 수정했다는 뜻이다. 재제출 대상인 앱이었던 것 같다. store/listing.md도 같이 수정됐으니 문구 자체도 바뀌었을 것.
1.0.5 제출, 그리고 아이콘 20개 백업
Salat 앱 1.0.5가 ASC에 올라갔다. WAITING_FOR_REVIEW, 자동출시 설정. CLAUDE.md에 제출 기록 남겼고, index.html의 앱 카드도 실측 상태로 동기했다. sync_apps --fetch로 실제 심사 상태를 가져와서 반영한 것이다.
아이콘 백업 커밋도 있었다. 앱 아이콘 교체 전에 원본 20개를 _bak/icons-20260809 디렉토리로 이관했다. arrowwise, camberpro, cncfeeds 등 여러 앱의 아이콘이 포함돼 있고, 날짜 기준은 8월 9일이다. 오늘 백업을 커밋했지만 파일 자체는 8월 9일 기준 스냅샷인 것으로 읽힌다. 교체 작업이 진행 중이거나 준비 단계인 것 같은데, 이 커밋은 교체 이전 상태를 보존해두는 안전장치다. 이런 걸 미리 챙겨두는 습관은 좋은 거다.
5.6 recovery 기록 수정
오늘 재밌었던 - 아니, 정확히는 살짝 헷갈렸던 - 건 plan-56-recovery.md 관련 커밋이 두 번 연달아 나온 부분이다.
첫 번째 커밋: 8/23 배치로 bloomly·pitlog 재제출, filmdev는 보류로 기록.
두 번째 커밋: 아니다, filmdev도 8/23에 제출됐다. 사용자 오버라이드. 정정.
두 커밋 사이에 실제로 제출이 이루어진 것 같다. 처음엔 보류로 계획 잡았다가, 직접 판단해서 제출하고, 그 결과를 문서에 반영한 거겠지. "사용자 오버라이드"라는 표현이 재밌다. 자동화나 계획된 배치에서 이탈해서 수동으로 결정을 바꿨다는 뜻인데, 문서 커밋에 그 맥락까지 남겨두는 게 나중에 보면 유용하다. "왜 계획이랑 다르게 됐어?"라는 질문에 커밋 히스토리가 답해주는 구조.
5.6 recovery 자체가 뭔지 커밋에서 더 이상 알 수 없지만, 여러 앱을 한꺼번에 재제출하거나 출시하는 배치 작업인 것 같다. bloomly, pitlog, filmdev 세 앱이 오늘 제출됐거나 예정된 것.
psy 자동 테스트
chore(psy): daily auto-generated tests (2026-08-23) 커밋. 심리 테스트 콘텐츠를 자동으로 생성하는 파이프라인이 돌아가고 있는 것 같다. 오늘은 세 개가 만들어졌다. "나쁜 소식 전달 스타일", "영화 보는 타입", "산 속 펜션 귀신". JSON 파일이랑 커버 웹피 이미지가 같이 올라왔다. 이건 직접 작업한 게 아니라 스케줄대로 생성된 결과물을 커밋에 담은 것이다. 자동화 파이프라인이 제대로 돌고 있다는 확인이기도 하고.
"산 속 펜션 귀신"은 제목이 독특하다. 심리 테스트 콘텐츠니까 뭔가 상황 제시형 문항인 것 같은데, 매일 이 정도 다양성으로 새 테스트가 나온다는 건 생성 파이프라인이 꽤 잘 잡혀 있다는 뜻이다.
오늘을 통으로 보면
커밋을 시간순으로 읽으면 보안 수습 - 인프라 정비 - 앱 스토어 제출 준비 - 기록 정정 - 자동화 확인의 흐름이다. 자연스럽게 묶이는 것들이 있고, 맥락 없이 사이에 끼어든 것들도 있다. 개발 리드 포지션에서 하루를 보내면 항상 이렇다. 집중해서 한 가지 문제를 파고드는 시간이 생각보다 적고, 끊임없이 맥락을 전환하면서 이것저것 처리한다.
보안 픽스 두 개는 오늘 제일 중요한 작업이었다. 그 중에서도 이메일 인증 우회 건은 실사용자 데이터에 직접 닿는 문제였으니 다른 것보다 더 빠르게 처리하는 게 맞았다.
스크린샷 v2 전환은 일이 많았는데 결과물이 ASC랑 Play까지 올라갔으니 일단 마무리는 됐다. 심사 결과가 나와봐야 알겠지만.
남은 숙제가 있다면 DML 룰을 하네스 문서 수준을 넘어서 파이프라인에서 강제하는 것, 그리고 분산락 전환 이후 모니터링을 며칠 지켜보는 것. 두 가지 다 오늘 안에 끝낼 수 없는 성격의 일이다.
댓글 0
첫 댓글 달아줘.