Indie Hackers

My App Trusted the Phone With Money. Twice.

앱이 돈을 맡긴 곳은 휴대폰이었습니다. 두 번이나.

Flutter와 Firebase로 만든 PeerPlay에서 지갑 잔액을 사용자가 직접 수정할 수 있었고, 테스터 보너스 지급도 휴대폰이 결정했습니다. 작성자는 금액과 보상 조건을 서버에서 처리하고 초기 추천 보상은 수동 검토하도록 바꿨습니다.

에디터 노트

금액 결정을 서버로 옮기는 수정은 잔액 위조와 클라이언트 조작을 막는 데 직접 대응합니다. 다만 서버 판단만으로 중복 지급까지 막지는 못합니다. 댓글에서 나온 캠페인과 테스터 조합의 멱등성 키는 재시도에도 한 번만 지급하도록 보완하며, 작성자도 이를 다음 단계로 확인했습니다. 초기 수동 검토는 작성자가 실제로 도입한 절차이고, 원장 대조와 규칙 테스트는 댓글에서 제안한 추가 방안입니다. 보상 원장과 잔액을 비교하는 탐지 절차까지 갖추면 예방 규칙 변경으로 다시 열린 구멍이나 지급 기록의 불일치도 찾을 수 있다는 점에서, 서버 권한 분리와 함께 다룰 만한 운영 과제입니다.

AI 요약

PeerPlay는 개발자끼리 앱을 시험하고 결과를 기록하는 Flutter·Firebase 앱입니다. 추천 보상과 테스터 보너스를 지갑에 지급하는 기능에서 작성자는 두 차례 클라이언트를 신뢰하는 실수를 발견했습니다. 화면은 정상적으로 보였지만 금액을 다루는 데이터와 지급 판단을 휴대폰에 맡겼습니다.

사용자 문서 쓰기 권한이 잔액까지 열었습니다

첫 번째 문제는 사용자가 자기 지갑 문서를 직접 수정할 수 있었다는 점입니다. 앱은 휴대폰에서 데이터베이스에 바로 연결했고, 보안 규칙은 사용자가 자기 문서를 쓸 수 있도록 허용했습니다. 이 규칙은 문서 안의 모든 필드에도 적용됐습니다. 사용자는 잔액처럼 돈을 나타내는 값까지 바꿀 수 있었습니다.

작성자는 추천 프로그램을 만들면서 이 문제를 확인했습니다. 추천 보상은 2달러이며, 추천받은 개발자가 자기 앱을 등록하고 다른 테스터 5명에게 각각 적격 사용일 3일 이상을 채우게 해야 합니다. 추천받은 사람 자신도 3일간 테스트해야 보상 심사 대상이 됩니다. 작성자는 추천 보상을 자동 지급하지 않고 각 추천인의 첫 세 건을 직접 검토하도록 했습니다. 그 뒤에는 자동 승인을 적용하지만, 기기 공유 정황이나 같은 지급 정보의 재사용처럼 위험 점수가 높으면 다시 수동 검토합니다.

테스터 보너스도 휴대폰이 판단했습니다

두 번째 문제는 유료 캠페인의 보너스 지급 방식에서 나왔습니다. PeerPlay의 첫 유료 캠페인은 여성 건강 및 생리 추적 앱 Nada를 위한 캠페인이었습니다. Starter Pro 상품으로 검증된 테스터 15명을 모집했고, 캠페인 기간은 2026년 9월 16일부터 10월 6일까지였습니다. 테스터 18명이 참여했고 11명이 14일 전체를 마쳤습니다. 적격 테스터 일수는 217일이었으며 Google은 이후 Nada에 프로덕션 액세스를 승인했습니다.

캠페인 진행 중 작성자는 유료 캠페인 로직을 다시 만들었습니다. 기존에는 테스터마다 별도 테스트 기간을 두고 휴대폰에서 보너스를 지급했습니다. 변경 후에는 참가자 모두가 같은 달력을 따르고, 14일 중 적격 사용일이 12일 이상이어야 보너스를 받습니다. 지급 판단과 처리는 서버로 옮겼습니다. 보상 조건을 충족했는지 휴대폰이 결정하는 구조가 잔액 수정 문제와 같은 신뢰 경계의 문제였기 때문입니다.

작성자가 정한 기준

휴대폰은 발생한 일을 보고하고 서버가 그 일이 보상 대상인지 판단해야 한다고 작성자는 설명합니다. 사용자가 소유한 문서라는 이유만으로 잔액이나 상태 필드까지 사용자가 쓸 수 있게 두지 말고, 보안 규칙에서 금액 필드를 잠가 서버 코드만 수정하게 해야 합니다. 보상은 클라이언트가 보내는 숫자가 아니라 실제 행동 기록을 바탕으로 계산해야 합니다. 초기 지급은 사람이 검토하고, 지급 기능을 추가할 때마다 해당 기능이 읽는 필드에 누가 쓸 수 있는지 점검하라고 제안합니다.

Indie Hackers 반응

  • @AmandaBrown — 솔로 개발자는 코드를 쓴 사람이 직접 테스트하기 때문에 이 문제가 생기기 쉽습니다. 의도한 경로를 알고 있으니 낙관적으로 테스트하게 됩니다. 클라이언트가 금액을 조작해 보내면 어떻게 될지 생각하려면 자기 코드를 잠시 불신해야 하는데, 방금 코드를 쓴 사람에게는 특히 어렵습니다. 돈이 걸린 기능은 서버에서 검증해야 합니다. 실제 습관으로는 curl로 엔드포인트를 호출하며 악의적인 클라이언트처럼 테스트하는 방법을 권합니다. 가정이 빠르게 무너집니다.
  • @meSingh — 지급 판단을 서버로 옮기면 누가 결정하는지는 해결됩니다. 다음으로는 그 판단이 몇 번 실행되는지 확인해야 합니다. Cloud Functions 이벤트 트리거는 최소 한 번 실행되므로 재시도 중 같은 "14일차 완료" 이벤트가 보너스를 두 번 지급할 수 있습니다. tester별로 campaignId_testerId 같은 고정 ID를 가진 지급 문서를 만들고 트랜잭션으로 생성하면 간단히 막을 수 있습니다. 두 번째 실행은 문서가 이미 있는지 확인하고 멈춥니다. 에뮬레이터에서 소유자가 자기 잔액을 쓰려 하면 거부되는지 보안 규칙 테스트도 추가하면 규칙을 바꿀 때 문제가 다시 생기는 일을 막습니다.
  • @letsmakeproduct — 돈 필드에 잔액 숫자 하나만 두지 않는 방법이 도움이 됐습니다. 백엔드는 "+2달러, 추천 보상, 날짜" 같은 거래 내역만 추가하고 잔액은 그 합계로 계산합니다. 지급 내역을 따로 기록하지 않아도 되고, 동시에 두 건이 들어와도 숫자 하나를 덮어쓸 때처럼 서로 지우지 않습니다. 지급 전 서버 검증은 여전히 필요하지만 실수를 추적하기는 쉬워집니다. 지갑 문제를 공개해줘서 고맙습니다. 사람들은 잘된 일만 이야기하는 경우가 많습니다.
  • @zaneberg — 두 문제 모두 화면에 드러나지 않았으니 예방뿐 아니라 탐지도 필요합니다. 매일 밤 지갑 잔액을 모두 더한 값과 그 잔액을 만든 보상 원장 내역의 합계를 비교하면 됩니다. 차이가 있으면 서버 경로 밖에서 돈을 쓴 기록입니다. 20줄이면 만들 수 있고 사용자가 알아채기 전에 세 번째 실수를 잡을 수 있습니다.
    • @Viktor Molnar — 제가 놓친 부분이 바로 이겁니다. 두 수정은 모두 예방에 관한 것이고, 저는 눈으로 이상한 점을 발견했을 때만 버그를 찾았습니다. 매일 원장 합계와 지갑을 비교했다면 두 문제를 훨씬 일찍 찾았을 겁니다. 20줄이면 바로 추가하겠습니다.
  • @ranamuzamil — Firebase 앱에서 "사용자가 자기 문서를 수정할 수 있다"는 규칙은 제가 가장 자주 보는 함정입니다. 안전해 보이지만 잔액 필드까지 넘겨줍니다. 보너스 지급을 서버로 옮긴 건 올바른 수정입니다. 첫 세 건을 수동 검토하는 방식도 좋습니다. 몇 분이면 되고, 미리 작성한 규칙보다 사기 수법을 더 많이 알려줍니다.
    • @Viktor Molnar — 맞습니다. 그 규칙은 완전히 무해해 보였습니다. "사용자는 자기 문서를 수정할 수 있다"는 말은 기본적인 보안처럼 들리지만 문서에 잔액이 있으면 이야기가 달라집니다. 수동 검토에도 동의합니다. 처음 몇 건을 살펴보며 미리 짐작한 것보다 사람들이 시스템을 어떻게 악용하려는지 더 많이 배웠습니다.
  • @drewdawg4207 — 같은 휴대폰을 두 번 믿은 건 큰 예외 상황입니다. 실제 돈이 움직인 뒤가 아니라 일찍 드러나 다행입니다. 해결책은 기기 지문 확인인가요, 두 번째 지급 전 확인 절차인가요?
    • @Viktor Molnar — "두 번"은 같은 휴대폰을 두 번 믿었다는 뜻이 아니라 서로 다른 실수 두 건을 말합니다. 첫째는 휴대폰에서 지갑 잔액을 직접 쓸 수 있었고, 둘째는 테스터 보너스를 서버가 아니라 앱이 지급했습니다. 기기 지문 확인이 아니라 모든 금전 판단을 서버로 옮겼습니다. 휴대폰은 요청만 하고 결정은 하지 않습니다.
  • @Bullpicks — 첫 지급을 직접 검토하는 건 지금 단계에서 가장 저렴한 사기 방지책입니다. 잔액 문제에는 금액 필드를 서버 소유로 두고 기기에서 받은 값을 그대로 쓰지 말고, 변경 때마다 저장된 값에서 새 잔액을 다시 계산하는 방식이 도움이 됩니다. 일일 작업이 거래 기록과 연결되지 않은 잔액 변경을 표시하게 하면 조용히 생긴 차이를 커피를 마시며 검토할 수 있습니다.
    • @Viktor Molnar — 두 의견 모두 동의합니다. 이제 금액 필드는 서버 소유이고 휴대폰에서는 전혀 쓸 수 없습니다. 거래 내역과 연결되지 않은 잔액 변경을 표시하는 일일 작업도 좋은 추가 사항입니다. "문제가 없기를 바란다"는 상태를 실제로 확인할 목록으로 바꿔줍니다.
  • @innokentyB — 판단을 서버로 옮기면 가장 큰 구멍은 막히지만 재시도와 중복 이벤트로 보너스가 두 번 지급될 수 있습니다. 불변인 적격 이벤트 집합에 각 보상을 연결하고 지급마다 별도의 멱등성 키를 두겠습니다. 그러면 서버는 조건이 맞는지만 확인하는 게 아니라 같은 지급 의무가 이미 이행됐는지도 확인합니다. 현재는 지급 건의 고유 식별자로 무엇을 쓰나요?
    • @Viktor Molnar — 좋은 지적입니다. 서버 수정은 큰 구멍을 막지만 재시도 문제는 남습니다. 지급 식별자는 캠페인과 테스터의 조합입니다. 조건 충족 이벤트가 몇 번 실행돼도 테스터 한 명은 캠페인마다 한 번만 지급받습니다. 이를 멱등성 키로 기록하고 지급 전에 확인하는 게 다음 단계입니다.

원문: Indie Hackers / 번역·요약: Trawling