구글 심사봇이 내 원격 명령을 먹어치웠다: 내부테스트 앱과 기기 스코프의 교훈
개인용 안드로이드 앱에 서버 원격 명령 기능을 붙였습니다. 서버 큐에 명령을 넣으면 앱이 동기화 때 받아 실행하는 단순한 구조인데, 어느 날부터 명령이 "실행 완료"로 표시되는데 정작 내 폰에는 아무 일도 일어나지 않았습니다. 범인은 뜻밖에도 구글이었습니다 — 플레이 스토어의 심사 자동화 기기들이 내 앱을 실행하면서 명령을 먼저 집어삼키고 있었던 겁니다. 1인용 앱에도 "사용자"가 나 혼자가 아니라는 이야기입니다.
1. 배경: 왜 개인 앱이 스토어를 거치는가
이 앱은 판매용이 아닙니다. 오직 제 폰 한 대를 위한 개인 도구입니다. 그런데도 플레이 스토어 내부테스트 트랙으로 배포하는 이유는 하나 — 요즘 기기들은 보안 정책상 사이드로드(APK 직접 설치)를 사실상 막아두었기 때문입니다. 개인 앱도 정식 유통 경로를 타야 하는 시대라, "나만 쓰는 앱"이 스토어 인프라 위에 올라가게 됐습니다. 그리고 이 선택이 뜻밖의 부작용을 데려왔습니다.
2. 발단: 완료됐는데 실행되지 않은 명령
원격 명령 구조는 소박합니다. 서버 DB에 명령 행을 넣으면(예: "이 기록 3건을 휴지통으로"), 앱이 다음 동기화 때 명령을 받아 실행하고 결과를 서버에 보고합니다. 어느 날 명령을 넣고 폰을 동기화했는데 아무 변화가 없었습니다. 서버 로그를 보니 더 이상했습니다.
command #12: status=완료, 결과 ok
(그런데 내 폰의 데이터는 그대로)
명령은 분명히 누군가에게 전달되어 실행됐습니다. 내 폰이 아닌 누군가에게.
3. 원인: 심사봇도 앱의 사용자다
동기화 수신 기록을 뒤지니 낯선 기기 식별자들이 있었습니다. 제 폰 말고도 서너 개의 기기가 주기적으로 앱을 실행해 서버와 동기화하고 있었던 겁니다. 정체는 플레이 스토어의 자동 심사·호환성 테스트 기기들이었습니다. 스토어는 업로드된 앱을 여러 가상/실물 기기에서 자동 실행해 보는데, 제 앱은 실행되면 성실하게 서버 동기화부터 합니다. 그 동기화가 명령 큐를 폴링하고 — 큐는 "먼저 온 놈이 가져가는" 구조였으니 — 심사봇이 내 폰보다 먼저 명령을 소진해 버린 겁니다.
심사봇의 기기에는 당연히 제 데이터가 없으니 명령은 "대상 없음"으로 조용히 성공 처리됐고, 서버는 완료 도장을 찍었습니다. 완벽한 침묵 실패입니다.
4. 해결: 수신자 없는 메시지를 금지한다
- 기기 스코프 필수화: 모든 명령에 대상 기기 ID를 강제했습니다. 명령 등록 도구의 기본값 자체를 제 실기기 ID로 박고, "전체 기기" 브로드캐스트는 의도적으로 어렵게 만들었습니다. 이제 심사봇이 폴링해도 자기 앞으로 온 명령이 없으니 그냥 지나갑니다.
- 기기 목록의 가시화: 이후 업데이트에서 기기별 앱 버전·마지막 동기화 시각을 서버에 기록하게 했습니다. 지금도 심사봇 기기 몇 대가 최신 버전으로 꼬박꼬박 동기화하는 게 보입니다 — 이제는 관찰 대상일 뿐, 명령을 건드리지 못합니다.
- 덤으로 얻은 것: 이 기기 테이블 덕에 "업데이트 됐나?"를 폰을 열지 않고 서버에서 확인할 수 있게 됐습니다. 부작용 수습이 기능이 된 셈입니다.
5. 교훈
- 1인용 앱에도 사용자는 여럿이다. 스토어에 올라간 순간 심사봇, 호환성 테스트 기기, 언젠가의 새 폰까지 전부 "실행 주체"가 됩니다. 서버는 그 전부를 상대합니다.
- 명령 큐에 브로드캐스트 기본값을 두지 마라. 수신자를 명시하지 않은 메시지는 언젠가 엉뚱한 수신자가 가져갑니다. 기본값이 안전해야 실수가 사고가 되지 않습니다.
- "완료"는 "의도대로 완료"가 아니다. 대상 없는 명령이 조용히 성공하는 설계는 침묵 실패를 낳습니다. 실행 결과에 "어느 기기에서, 몇 건에 적용"까지 담아야 완료 도장이 의미를 가집니다.
- 봇 트래픽은 데이터를 오염시킨다. 심사봇의 동기화가 사용 통계였다면 통계가, 명령 큐였으니 명령이 오염됐습니다. 스토어 배포 앱의 서버 지표는 봇 기기를 걸러야 진짜가 보입니다.
FAQ
Q. 심사봇이 앱을 실행하는 걸 막을 수는 없나요?
없습니다. 자동 실행 검사는 스토어 프로세스의 일부입니다. 막을 수 없는 트래픽이라면, 서버 쪽에서 "정상적이지만 무해하게" 다뤄지도록 설계하는 것이 답입니다.
Q. 명령에 인증을 붙이면 되지 않았나요?
인증은 이미 있었습니다 — 심사봇도 정식 설치된 앱을 실행하는 것이니 같은 토큰으로 통과합니다. 문제는 인증이 아니라 수신 범위였습니다. "누구든 인증된 기기"와 "바로 그 기기"는 다른 개념입니다.
Q. 내부테스트 말고 다른 배포 방법은 없나요?
기기 제조사의 개발자 모드 우회나 기업용 배포(MDM) 같은 길도 있지만, 개인 규모에선 내부테스트 트랙이 가장 마찰이 적습니다. 심사봇과의 동거는 그 대가이고, 이 글이 그 동거 계약서의 깨알 조항쯤 됩니다.


