mcpField
R
rc-security

rc-security

github.com/revenuecatセキュリティ76665 回インストール1 日前に更新
リポジトリ

rc-security について

rc-securityは、AndroidアプリのRevenueCat購入と権限に関するセキュリティを確認するスキルです。検証モード、APIキー、ユーザーID、サーバー側のアクセス判定を点検します。

主な機能

  1. 01権限検証モードの確認
  2. 02公開鍵と秘密鍵の分離
  3. 03認証済みIDによるユーザー識別
  4. 04サーバー側で権限を確認

活用シーン

  1. 01リリース前のRevenueCat設定監査
  2. 02Androidソース内の秘密鍵を調査
  3. 03ログインユーザーに匿名IDを置き換え
  4. 04権限チェックでプレミアム機能を保護

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/revenuecat/ai-toolkit --skill rc-security

インストール後、Claude Code で/rc-securityを入力して読み込まれたか確認します。

詳細

フェーズ 0:意図

ユーザーに次のように伝えます。「Android における RevenueCat のセキュリティ状況を確認します。確認モード、API キー、ユーザー ID、サーバー側のアクセス判定を確認します。」

フェーズ 1:調査

RevenueCat がすでに対応している内容を確認し、実際の不足点に集中できるようにします。

懸念事項担当方法
Google Play に対するレシートの検証RevenueCat バックエンドawaitPurchase() が返る前に実行されます
購入トークンの再利用RevenueCat バックエンドトークンはサーバー側で重複排除されます
偽造された購入トークンRevenueCat バックエンドGoogle Play の検証に失敗し、エンタイトルメントは付与されません
RevenueCat への HTTPS 通信SDK常に有効です
ネットワーク障害時のトークン送信の再試行SDK次回のアプリ起動時に再試行されます

プロジェクトに次の質問をします。

  • EntitlementVerificationMode は設定されていますか?設定されていない場合、CustomerInfo の応答は署名を確認せずに信頼されます。
  • SDK は公開 Android キー(goog_ で始まるもの)で設定されていますか?それとも、誰かが誤ってシークレットキーをクライアントに貼り付けていませんか?
  • 実ユーザーは Purchases.logIn(yourUserId) で識別されていますか?それとも、アプリはまだ匿名の $RCAnonymousID:... に依存していますか?
  • プレミアムコンテンツを提供するサーバーエンドポイントは、サーバー側でエンタイトルメントを確認していますか?それとも、クライアントから送られたフラグを信頼していますか?

フェーズ 2:計画

コードを変更する前に、以下の各項目を決定します。

決定 A:エンタイトルメントの確認モード
モード確認に失敗した場合の動作選択する状況
DISABLED(デフォルト)署名は行われませんすばやくプロトタイプを作る場合のみ
INFORMATIONALログに記録されますが、アクセスは引き続き許可されます実ユーザーへの誤った拒否のリスクを負わずに状況を把握したい場合
ENFORCEDEntitlementInfo.isActive が false を返します厳格なクライアント側の保証と引き換えに、プロキシや VPN による誤検知を許容できる場合
決定 B:権限の決定元はどこですか?

ENFORCED であっても、有料コンテンツの権限の決定元はクライアントではありません。各プレミアムエンドポイントで、次のどちらを採用するかを決定します。

  1. リクエストごとに RevenueCat REST API を呼び出す、または
  2. RevenueCat ウェブフックによって更新されるローカルの has_entitlement フラグを読み取る。

オプション 2 はリクエスト時のコストが低く、オプション 1 はキャッシュの古さが発生しません。どちらかを選び、文書化します。理由なくエンドポイントごとに使い分けないでください。

決定 C:ユーザー ID

アプリが本番環境で稼働している場合は、独自の認証済みユーザー ID を使って Purchases.logIn("your_user_id") を呼び出す計画を立てます。匿名 ID はデバイス単位の識別子であり、認証情報ではありません。同じデバイスを使う複数のユーザー間で共有されます。

フェーズ 3:実行

3.1 応答の検証を有効にする

PurchasesConfiguration にモードを追加します。

PurchasesConfiguration.Builder(context, apiKey)
    .entitlementVerificationMode(EntitlementVerificationMode.INFORMATIONAL)
    .build()

エンタイトルメントを確認するときに、検証結果を読み取ります。

when (customerInfo.entitlements.verification) {
    VerificationResult.VERIFIED -> { /* response is authentic */ }
    VerificationResult.FAILED -> { /* possible tampering, log and alert */ }
    VerificationResult.NOT_REQUESTED -> { /* verification disabled */ }
    VerificationResult.VERIFIED_ON_DEVICE -> { /* verified locally */ }
}

実際のトラフィックで FAILED がまれであることを示すテレメトリを確認できたら、ENFORCED に切り替えます。

.entitlementVerificationMode(EntitlementVerificationMode.ENFORCED)

ENFORCED では、署名に失敗すると、影響を受けるエンタイトルメントの isActive が false に変わります。

3.2 API キーを適切な場所に保管する
キー保管場所可能な操作
公開 Android SDK キー(goog_...)アプリのバイナリに埋め込み呼び出し元のユーザーに対する読み取りと購入のみ
シークレット REST API キーサーバー、シークレットマネージャー、または環境変数REST API 全般、管理者操作、エンタイトルメントの付与

Android コードには、公開キーのみを記載します。

Purchases.configure(
    PurchasesConfiguration.Builder(context, "goog_PUBLIC_android_sdk_key").build()
)

シークレットキーをアプリのリポジトリにコミットしてはいけません。リリース前に、Android のソースツリーでシークレットキーのプレフィックスを grep し、一致がゼロ件であることを確認します。

3.3 実ユーザーを識別する

認証済みユーザー ID を取得したら、すぐに logIn を呼び出します。

val result = Purchases.sharedInstance.awaitLogIn("your_user_id")
val customerInfo = result.customerInfo

匿名 ID を認証情報として使用しないでください。これはデバイス単位の識別子であり、共有デバイス上の購入履歴を保護しません。

3.4 クライアントではなくサーバー側で制御する

ENFORCED モードであっても、有料コンテンツを提供するすべてのサーバーエンドポイントで、サーバー側のエンタイトルメントを確認します。

def get_premium_content(user_id):
    info = revenuecat.get_subscriber(user_id)
    if not info.entitlements["pro"].is_active:
        raise Forbidden()
    return content

または、RevenueCat のウェブフックによって更新されるローカルの has_pro フラグを読み取り、リクエストごとにそのフラグを確認します。

フェーズ 4:確認

  • Android のソースには公開 SDK キーのみが含まれています。シークレットキーの grep で該当がないことを確認してください。
  • EntitlementVerificationMode が設定されており(DISABLED ではない)、VerificationResult.FAILED のテレメトリーを監視しています。
  • 実際のユーザーは Purchases.logIn で識別されています。匿名 ID はログイン前のフローでのみ使用してください。
  • サーバー上の有料コンテンツのすべてのエンドポイントで、REST API または Webhook によるフラグを通じてエンタイトルメントを検証しています。クライアントのヘッダーを信頼しているエンドポイントはありません。

よくある間違い

間違い問題点対処法
アプリにシークレットキーを含めて配布する攻撃者が APK を逆コンパイルすると、管理用 REST エンドポイントを呼び出せますアプリには公開キーを含め、シークレットキーはサーバーにのみ保管します
本番環境でモードを DISABLED のままにする中間者攻撃によって CustomerInfo のレスポンスを偽造される可能性がありますINFORMATIONAL または ENFORCED を設定します
匿名 ID を認証情報として扱う匿名 ID は秘密ではなく、同じデバイスを使うユーザー間で共有されます認証済みユーザー ID を指定して logIn を呼び出します
サーバーでクライアントからの customerInfo を信頼する改ざんされたクライアントは、任意のエンタイトルメントを申告できますREST API または Webhook による DB フラグを通じてサーバー側で検証します

参考資料

コメント