mcpField
S
spree-security

spree-security

github.com/spreeセキュリティ10225 回インストール4 週間前に更新
リポジトリ

spree-security について

Spree 6の拡張開発者向けセキュリティスキルです。シークレット、APIアクセス、スタッフセッション、顧客向けエンドポイントを扱い、認証情報、決済、店舗間データのリスクを抑える方法を説明します。

主な機能

  1. 01シークレットと暗号化キーの管理
  2. 02APIキーのスコープ設定
  3. 03スタッフセッションの保護
  4. 04店舗単位のレコードアクセス
  5. 05一括代入の制御

活用シーン

  1. 01顧客データやゲートウェイ情報の暗号化キーを設定
  2. 02APIキーのスコープで連携権限を制限
  3. 03カスタムコントローラーの店舗間データ露出を点検
  4. 04APIリソースの書き込み可能属性を設定

インストール

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

$npx skills add https://github.com/spree/agent-skills --skill spree-security

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

詳細

Spree のセキュリティ

Spree は Rails のセキュリティモデルを継承し、決済フロー、顧客の個人識別情報(PII)、スタッフの認証情報、送信 Webhook など、eコマース特有の攻撃対象領域を追加します。このスキルでは、Spree 6 を拡張する開発者が適切に対処すべき内容を説明します。誰が何をできるか(ロール、権限キー、スコープ、ストアフロントの所有権、SSO)については、spree-auth-permissions を参照してください。

4 行でわかる脅威モデル

  1. Store API:インターネットに公開され、信頼できないクライアントから呼び出されます。リスク:カートや注文に対する IDOR、カタログコンテンツを介した XSS、カート/認証エンドポイントの悪用。
  2. Admin API + ダッシュボード:スタッフの認証情報がフィッシングされ、インテグレーションからキーが漏えいします。リスク:過剰なスコープを持つシークレットキー、カスタムコントローラーでのストア間の情報漏えい、ロールの権限の肥大化。
  3. 決済:金銭と PCI に関わります。リスク:ログや DB へのカード情報の記録、ゲートウェイ Webhook の未検証、返金の悪用。
  4. 送信トラフィック:Webhook とインテグレーションが対象です。リスク:SSRF、受信先の未検証。

シークレット

  • Rails の暗号化済みクレデンシャルまたは環境変数に保管し、リポジトリには絶対に含めないでください。VITE_* 変数はダッシュボードのバンドルにコンパイルされるため、絶対にシークレットを設定しないでください。
  • シークレットが漏えいした場合: まずプロバイダー側でローテーションし、その後クレデンシャル/環境変数を更新してデプロイし、最後に Git の履歴を消去します(git filter-repo)。先に履歴を消去すると、漏えいしたキーはローテーションするまで使い続けられます。
  • secret_key_base は環境ごとに固定してください。 シークレット API キーはこれをキーとする HMAC-SHA256 ダイジェストとして保存されるため、これをローテーションするとすべての sk_ キーが無効になります。また、JWT 署名の最後のフォールバックとしても使われます。SPREE_JWT_SECRET_KEY(またはクレデンシャルの jwt_secret_key)で専用の JWT シークレットを設定してください。
  • Active Record の暗号化。 Spree は、キーが設定されている場合に限り、Spree::WebhookEndpoint#secret_key と Spree::GatewayCustomer#profile_id(決定論的)、および Spree::UserIdentity#access_token/#refresh_token(OAuth トークン)を暗号化します。キーがない場合は平文になり、スターターは起動時に警告をログに出力します。 次の 3 つの環境変数をすべて設定してください:ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY、ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY、ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT。
    • create-spree-app は開発用のキー一式を .env に書き込みます。古いプロジェクトの .env に追加するには spree encryption init を使います(既存のキーは上書きせず、--force もありません。その後、spree update でコンテナを再作成してください。eject 済みの場合は spree dev を使います。spree restart では古い環境変数が維持されます)。本番環境用の新しいキー一式は、spree encryption init --print または bin/rails db:encryption:init で出力できます。環境ごとに別のキー一式を使い、シークレットマネージャーにバックアップしてください。
    • スターターの config/application.rb は、環境変数(なければ active_record_encryption クレデンシャルの設定)を config.active_record.encryption にコピーします。これは重要です。Webhook シークレットとゲートウェイ顧客の encrypts 呼び出しは Rails.configuration.active_record.encryption を参照するため、このスニペットがない状態でキーをクレデンシャルのみに保存すると、この 2 つのカラムは平文のままになります。古いスターターを使ったアプリには、このスニペットが必要です(spree-upgrade-5-to-6 を参照)。
    • データを暗号化した後は、キーを変更したり失ったりしないでください。 Rails ではプライマリーキーをローテーションできますが、Webhook シークレットとゲートウェイ顧客 ID に使う決定論的キー/ソルトはローテーションできません。
    • 既存データの暗号化を有効にする場合:ID トークンは読み取り可能な状態を維持し(support_unencrypted_data: true)、次回の書き込み時に暗号化されます。Webhook シークレットとゲートウェイ顧客 ID はそうなりません。config.active_record.encryption.support_unencrypted_data = true と extend_queries = true を設定し、キーを設定してデプロイした後、[Spree::WebhookEndpoint, Spree::GatewayCustomer, Spree::UserIdentity].each { |m| m.find_each(&:encrypt) } を実行し、その後両方の設定を削除してください。
  • 決済方法とインテグレーションの設定は暗号化されません。 ゲートウェイの認証情報はシリアライズされた preferences カラムに保存されるため、データベースとそのバックアップには有効なシークレットが含まれているものとして扱ってください。有効なゲートウェイキーは、シードではなくダッシュボード(設定 → 決済)から入力してください。

spree/agent-skills プラグインには、エージェントが既知の形式のシークレット(Stripe のライブキー、AWS キー、PAT)を書き込もうとしたときに警告するフックが付属しています。これは警告用のトリップワイヤーであり、レビューの代わりにはなりません。

API キー

pk_*  Publishable. Safe in browser and mobile code. Store API only.
sk_*  Secret. Server-to-server only. Never ship it in a bundle, app or VITE_* variable.
  • 最小限のスコープ。 spree api-key create --type secret --scopes read_orders,write_fulfillments を実行してください。インテグレーションに write_all を付与しないでください。スコープ一覧は spree-api-v3/references/scopes.md にあります。
  • スコープとチャネルの紐付けは変更できません。アクセスをローテーションまたは変更するには、新しいキーを発行してデプロイした後、古いキーを失効させます(設定 → API キー、または spree api-key revoke)。
  • シークレットキーで行った書き込みは、そのキーに帰属します(*_type: api_key)。そのため、アクターフィールドと WebhookDelivery ログを通じて、漏えいしたキーによる操作を監査できます。
  • ストアフロントの公開可能キーをそのチャネルに紐付けることで、買い物客が X-Spree-Channel を送信してチャネルを切り替えられないようにします。
  • pk_ の漏えいは軽微な問題です。レート制限を設定してローテーションしてください。sk_ の漏えいは情報漏えいインシデントです。キーを失効させてから監査してください。

ダッシュボードとスタッフセッション

React ダッシュボード(@spree/dashboard。spree_dashboard によって /dashboard で配信)は、API キーを保持しません。

  • スタッフがサインインすると、JWT はメモリ内にのみ保存されます(デフォルトの有効期間は 5 分、admin_jwt_expiration)。リフレッシュトークンは、/api/v3/admin/auth をスコープとする HttpOnly の署名付き Cookie です。JavaScript から読み取ることはできず、リフレッシュのたびにローテーションされます。
  • HTTPS では Cookie は SameSite=None; Secure です。通常の HTTP では SameSite=Lax にフォールバックします(開発環境のみ)。
  • 同一オリジン(デフォルト:Rails が /dashboard でダッシュボードを配信)の場合、CORS や Cookie の設定は不要です。
  • クロスオリジン(CDN 上のダッシュボード)の場合、両側で HTTPS を使用し、さらに Settings → Allowed Origins(Spree::AllowedOrigin、/api/v3/admin/allowed_origins)にダッシュボードのオリジンを追加します。スターターの config/initializers/cors.rb は、credentials: true を指定して /api/v3/admin/* の許可オリジンをそのテーブルで確認します。Cookie の CSRF 保護は SameSite と厳格なオリジン許可リストによって行われるため、リストは正確かつ短く保ってください。
  • UI での表示制御は認可ではありません。 ダッシュボードでは GET /api/v3/admin/me の権限キーに基づいてボタンを非表示にしますが、それらを強制するのは API のゲートです。ダッシュボードプラグインのアクションはすべて、scoped_resource を宣言するエンドポイントを呼び出す必要があります(spree-auth-permissions を参照)。このゲートを通過した後のレコード単位の拒否は Spree.ability_class によって行われます。カスタムサブクラスでスタッフのアクセスをさらに制限できますが、キーゲートを緩めることはありません。また、シークレットキー(Spree::ApiKeyAbility)はこのゲートを回避します。
  • スタッフ SSO:OidcStrategy を登録し、必要に応じて Spree.admin_authentication_strategies から :email を削除します。スタッフアカウントは IdP から自動プロビジョニングされません。
  • スタッフ管理のガードレールは組み込みです(詳細は spree-auth-permissions を参照)。admin ロールを付与または削除できるのは管理者のみで、ストアには少なくとも 1 人の管理者が必要です。招待にはロールを指定する必要があります。招待の承認リンクは、書き込み権限で保護された別のエンドポイントから取得し(一覧から取得することはできません)、再送時にはトークンがローテーションされます。スタッフのパスワードリセットリンクは、ダッシュボードのオリジンのみを参照します。ログイン、現在のパスワードの確認、招待の承認では、共通のロックアウト(Spree::Authentication::Lockout)を使用します。追加するパスワード確認も、必ずこれを経由させてください。
  • スタッフにはそれぞれ個別のアカウントと、最小権限のロールを付与してください。admin@ のログインを共有しないでください。
Mission Control(/jobs)

ジョブ UI は HTTP Basic 認証で保護されています。本番環境では MISSION_CONTROL_USER と MISSION_CONTROL_PASSWORD を設定してください。これらが設定されていない場合、ダッシュボードはロックされたままです。スターターのローカルデフォルト(spree / spree123)が適用されるのは開発環境とテスト環境のみです。本番環境の環境ファイルにコピーしないでください。

Store API:IDOR とストア間の情報漏えい

  • カートと注文は、その所有者(JWT)またはゲストトークン(X-Spree-Token)を持つユーザーが読み取れます。トークンを持つ人は誰でも、そのカートや注文を読み取れるため、トークンをログに記録したり、共有する URL に含めたりしないでください。
  • プレフィックス付き ID は秘密情報ではありません(連番キーをエンコードしたものです)。すべての検索で認可を確認し、ID を推測しにくいことを前提にしないでください。
  • カスタム Store コントローラーでは、storefront_access_policy.scope(Model.for_store(current_store)) または current_user.<association> を通じて読み取ってください。Model.find(params[:id]) は使用しないでください。
  • カスタム Admin コントローラーでは、継承した scope(ストアでスコープ済み)を使用し、scoped_resource を宣言してください。マルチストアアプリはデータベースを共有しているため、コントローラー内の Spree::Order.find はストア間の情報漏えいにつながります。
  • 書き込み本文内のすべての ID も検索対象です。 参照先の ID(stock_location_id、reason_id、カスタムフィールドの親、翻訳対象、価格リストのバリアント、プロモーションルールの商品)を、current_store(セラー向けの画面ではセラーも含む)を通じて解決し、親レコードはクエリパラメーターや本文パラメーターではなくルートから取得してください。コアのエンドポイントではこの処理を行っていますが、パラメーターから生の *_id を割り当てるカスタムコントローラーを作ると、ストア間の情報漏えいの穴が再び開きます。
  • 開発環境とテスト環境では、Spree::StoreScopeGuard が、ストア所有テーブル(store_id を持つ任意の spree_* テーブル)に対する SELECT のうち、ストアでスコープされていないもの、または ID で絞り込まれていないものを検出します。API v3 のすべてのリクエストと、Spree::Current.store を設定するすべての作業単位(ジョブ、Webhook コントローラー、コンソールスクリプト、テスト)を、Spree::Current がリセットされるまで監視します。モード:SPREE_STORE_SCOPE_GUARD / Spree::Config[:store_scope_guard] = log(デフォルト)、raise(Spree 独自の API テストスイート)、off。本番環境では有効になりません。意図的にグローバル検索を行う場合は、Spree::StoreScopeGuard.skip { … } で囲んでください。警告を真剣に受け止めてください。

一括代入

API v3 のコントローラーは、resource_permitted_attributes(カスタムコントローラー)と各モデルの additional_permitted_attributes を使って許可リストを作成します。Spree::PermittedAttributes モジュールはありません。拡張機能が追加した項目が失われるため、permitted_params をオーバーライドしないでください。

# Make an extension column writable on the existing endpoints (never `<<`, the array is frozen):
Spree::Product.additional_permitted_attributes += [:brand_id]
Spree::Address.additional_permitted_attributes += [{ tag_ids: [] }]

# Custom controllers list their own:
def resource_permitted_attributes = %i[name rating body]

params.permit! は使用しないでください。顧客向けエンドポイントでは、所有権や権限に関わるカラム(customer_id、store_id、role_ids、status)を許可しないでください。これらはサーバー側で設定してください。

インジェクションと XSS

  • SQL: パラメーター化された where('x > ?', v) またはハッシュ条件を使用し、文字列補間は決して使用しないでください。Ransack は許可リストに登録された属性のみをフィルタリングするため、安全です。不明な q[...] 述語は黙って破棄されます。新しいフィルターは Spree.ransack.add_attribute(Model, :attr) で公開し、シークレットやダイジェストを許可リストに登録しないでください。フィルターははい/いいえの判定に使えるため、Store API と Seller API の許可リストはスタッフ向けよりも狭く設定されています(:store/:seller をキーとする storefront_ransackable_associations、private_ransackable_attributes/private_ransackable_scopes)。許可リストを広げる際は、非公開データをストアフロント用リストに含めないでください。Store/Seller の ResourceController を継承するコントローラーでは、自動的に設定されます(ransack_auth_object)。顧客向けコントローラーで独自にクエリを実装する場合は、ransack(params, auth_object: :store) を明示的に呼び出してください。
  • リッチテキスト: 商品、カテゴリー、コレクション、販売者の説明は、Spree::RichTextSanitizer により保存時にサニタイズされます(has_spree_rich_text/sanitizes_rich_text 経由)。許可リストはダッシュボードの Tiptap エディターが生成する内容をカバーしています:p br hr h1–h6 strong em s u code pre blockquote ul ol li a img。data:/javascript: URL は除去されます。API は description をプレーンテキストとして、description_html をサニタイズ済みマークアップとして返します。
    • 許可リストを意図的に広げるには、イニシャライザーに Spree::RichTextSanitizer.allowed_tags += %w[table thead tbody tr th td] を記述します。
    • コールバックをスキップする書き込み(update_columns、update_all、生 SQL、モデルを経由しない一括インポート)はサニタイズされません。Spree::RichTextSanitizer.sanitize(html) を自分で呼び出してください。
    • モデルに新しいリッチテキスト列を追加する場合:include Spree::SanitizableRichText と has_spree_rich_text :body を追加します。翻訳属性には、Translation クラスにも sanitizes_rich_text が必要です。
  • ストアフロント: *_html は、サーバー側でサニタイズされたこれらのフィールドに限り、フレームワークの生 HTML 出力方法(たとえば React の dangerouslySetInnerHTML)でレンダリングしてください。それ以外はすべてエスケープしてください。
  • CSV エクスポート: スタッフがスプレッドシートで開くファイルに書き込む前に、=、+、-、@ で始まるセルを無害化してください。

CORS と CSP

# config/initializers/cors.rb: storefront on another origin (the admin block comes from the starter)
allow do
  origins 'https://shop.example.com'
  resource '/api/v3/store/*', headers: :any, methods: %i[get post patch put delete options]
end
  • credentials: true と origins '*' を決して併用しないでください。管理画面と、オリジンをまたぐ販売者パネルでは、Allowed Origins テーブルを使用する必要があります。
  • HTML をレンダリングするアプリに CSP を設定してください。Next.js のストアフロントの場合は Rails ではなく Next アプリに設定します。Rails 側は主に JSON とダッシュボードを配信します。

Webhook

アウトバウンド(Spree → お客様の受信先)。各配信には、エンドポイントのシークレットを使用した "#{timestamp}.#{raw_body}" の HMAC-SHA256 の 16 進数値である X-Spree-Webhook-Signature と、X-Spree-Webhook-Timestamp、X-Spree-Webhook-Event が含まれます。受信側は次の処理を行う必要があります。

  1. JSON を解析する前に、未加工の本文バイト列を使って検証します。
  2. 定時間比較関数(ActiveSupport::SecurityUtils.secure_compare、crypto.timingSafeEqual)で比較します。
  3. 約 5 分より古いタイムスタンプは拒否します(リプレイ対策)。
  4. 冪等に処理します。2xx 以外の応答、タイムアウト、接続エラーは Spree::WebhookDeliveryJob によってバックオフ付きで再試行されます(合計 5 回。同じイベント id が使われます)。手動で再配信した場合も、同じイベントが再度送信されます。

@spree/sdk/webhooks は verifyWebhookSignature(rawBody, signature, timestamp, secret, tolerance = 300) をエクスポートします。spree-events-webhooks を参照してください。

SSRF。 開発環境以外では、配信は SsrfFilter を経由し、プライベート、ループバック、リンクローカルの宛先がブロックされます。開発環境では localhost の受信先を使用できるよう、このフィルターを回避します。Rails.env.development? の分岐を他のコードパスにコピーしないでください。webhooks_verify_ssl は開発環境以外ではデフォルトで有効です。有効のままにしてください。Webhook エンドポイントの作成には write_webhooks が必要です。この権限は意図的に settings とは分離されています。

write_webhooks/read_webhooks で許可されない操作。 customer.password_reset_requested には有効なリセットトークンが含まれます。このイベントが届くのは、イベント名を指定したエンドポイントのみです(* や customer.* には届きません)。このイベントの購読、または受信するエンドポイントの転送先変更には、write_customers も必要です。配信ログでは、認証情報(カートの token、リセット/検証トークン、ダウンロード URL、決済セッションのシークレット、ギフトカードの code)は [REDACTED] として保存されます。また Admin API が配信の payload を返すのは、基になるレコードを読み取れる呼び出し元のみです(それ以外の場合は null)。

インバウンド(ゲートウェイ → Spree。例:/api/v3/webhooks/payments/...):レコードは接頭辞付き ID のみで検索されます(リクエストでストアを指定することはできません)。リクエストは、その決済方法/インテグレーションのストア内で実行され、プロバイダーはレコード固有のシークレットを使って署名を検証します。署名が無効な場合は 401 を返します。認証手段は署名のみです。カスタムプロバイダーを作成する場合は、処理を行う前に署名を検証してください(spree-providers、spree-payments を参照)。

データプライバシー(GDPR)

  • データ主体からのリクエストは Spree::DataRequest レコードです。顧客は POST /api/v3/store/customers/me/data_requests { kind: "access" | "erasure" } でリクエストを作成します(消去には current_password が必要です)。スタッフは GET /api/v3/admin/customers/:id/export と POST /api/v3/admin/customers/:id/anonymize を使用します。エクスポートはバックグラウンドで作成され、有効期限付きの署名済みリンクとしてメールで送信されます。
  • 消去とは匿名化を意味します。これは Spree::Customers::Anonymize ワークフローで実行されます。アカウント、住所録、注文の住所スナップショット、保存済みカード、ID、セッションの情報を消去し、同意レコードからメールアドレス、IP、ユーザーエージェントを削除します(レコード自体は保持されます。下記の「同意」を参照)。財務記録(注文、決済、税明細、明細項目に加え、税務管轄判定のための国、州、切り詰められた郵便番号)は保持され、customer.anonymized が公開されます。
  • 個人データを保持するテーブルを追加する場合は、同じ変更で匿名化処理も拡張してください。 Core には、個人データのカラムが匿名化の対象に含まれていない場合に失敗するスキーマガードの spec があります。
  • フック:
    Rails.application.config.after_initialize do
      Spree.hooks.register('customers.anonymize.validate', 'MyApp::LegalHold')            # workflow.reject!('Under legal hold')
      Spree.hooks.register('data_requests.fulfill.extend_payload', 'MyApp::LoyaltyExport') # return a Hash to merge
    end
    
  • 同意: Spree::ConsentRecord は、同意イベント(目的、ソース、時刻、ドキュメントのダイジェスト)を記録します。顧客には email_marketing_consent_updated_at/_source もあります。同意レコードは消去後も保持され(証明として目的、ソース、タイムスタンプを維持)、メールアドレス、IP、ユーザーエージェントは消去されます。また、顧客がオプトインしていた場合、消去時に email_marketing の撤回レコード(ソースは anonymization)が追加されます。Cookie の同意はストアフロントの責任です。
  • スタッフによる DataRequest と ConsentRecord へのアクセスには、read_customers/write_customers が適用されます。

PCI の対象範囲

Spree は PAN を保存も送信もしません。カードデータは、ゲートウェイのホステッドフィールド(spree_stripe 経由の Stripe Elements / Payment Element、Adyen Drop-in、PayPal)を通じて、ブラウザーからゲートウェイへ送られます。Spree が受け取るのはトークンと決済セッションのみです。Spree::CreditCard に保持されるのはブランド、末尾 4 桁、有効期限、ゲートウェイ参照情報であり、カード番号全体や CVC は保持されません。

  • カードデータ用のカラムは追加しないでください。また、API 経由でカードの生データを中継しないでください。その必要があると思われる場合は、代わりにゲートウェイのトークン化を使用してください。
  • ホステッドフィールドのみを使用する場合、通常は SAQ A に該当します。カードデータを独自に収集すると、SAQ D に該当します。
  • パラメーターのフィルタリングでは、すでに number、verification_value などが対象になっています。独自の機密パラメーター名を追加する場合は、Rails.application.config.filter_parameters を拡張してください。

レート制限

API には次のスロットリングが組み込まれています。公開可能キー + IP アドレスごと(300 回/分)、シークレットキーごと(600 回/分)、ログイン、登録、トークン更新、パスワードリセットの IP アドレスごとの制限です。カウンターは Rails.cache に保存されるため、複数プロセスでデプロイする場合は共有ストア(Solid Cache または Redis)が必要です。大量の不正アクセスを防ぐため、前段に CDN または WAF を配置してください。表については spree-api-v3 を参照してください。

依存関係の衛生管理

CI で、ストアフロントとダッシュボードに対して bundle audit、brakeman、pnpm audit を実行してください。このプラグインには CI ワークフローは含まれていません。

デプロイチェックリスト

  • シークレットは credentials/env に設定します。VITE_*、リポジトリ、シードにはシークレットを含めません。
  • secret_key_base を固定し、SPREE_JWT_SECRET_KEY を設定します。3 つの ACTIVE_RECORD_ENCRYPTION_* キーを設定し(本番環境専用の値を設定してバックアップします)、config.active_record.encryption に読み込みます。
  • config.force_ssl = true を設定します。API、ダッシュボード、ストアフロントで HTTPS を使用します。
  • Allowed Origins には、実際のダッシュボード/販売者パネルのオリジンのみを指定します。ストアフロントの CORS には、オリジンを明示的に指定します。
  • MISSION_CONTROL_USER / MISSION_CONTROL_PASSWORD を設定します。
  • インテグレーションでは、必要最小限のスコープを持つシークレットキーを使用し、それぞれの目的に応じた名前を付けます。write_all は緊急時の使用に限定します。
  • スタッフには個別のアカウントと最小権限のロールを割り当てます。組織に IdP がある場合は、SSO を必須にします。
  • Webhook 受信側で HMAC を検証し、タイムスタンプを確認して、冪等性を確保します。
  • カスタムコントローラーは、ストア単位(Admin)または所有者単位(Store)でスコープを制限します。
  • 追加した個人データ用テーブルも匿名化の対象に含めます。
  • レート制限用に共有キャッシュストアを使用します。前段に CDN/WAF を配置します。
  • データベースのバックアップは暗号化し、データベースとは別の場所に保存します。

詳細情報

  • PCI:node_modules/@spree/docs/dist/developer/security/pci_compliance.md
  • データプライバシー:node_modules/@spree/docs/dist/developer/core-concepts/data-privacy.md
  • ダッシュボードのデプロイと認証:node_modules/@spree/docs/dist/developer/dashboard/deployment.md
  • Admin API の認証:node_modules/@spree/docs/dist/api-reference/admin-api/authentication.md
  • バックグラウンドジョブ / Mission Control:node_modules/@spree/docs/dist/developer/deployment/background_jobs.md
  • Rails セキュリティガイド:https://guides.rubyonrails.org/security.html
  • 関連スキル:spree-auth-permissions、spree-api-v3、spree-events-webhooks、spree-payments、spree-deployment。

コメント