spree-security

Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.

17 stars

Best use case

spree-security is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.

Teams using spree-security should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/spree-security/SKILL.md --create-dirs "https://raw.githubusercontent.com/OrcaQubits/agentic-commerce-skills-plugins/main/dist/antigravity/spree-commerce/.agent/skills/spree-security/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/spree-security/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How spree-security Compares

Feature / Agentspree-securityStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Spree Security

## Before writing code

**Fetch live docs**:
1. Check https://github.com/spree/spree/security for the security policy and known advisories.
2. Cross-reference https://rubysec.com for Rails / Ruby gem CVEs.
3. Inspect `Spree::Ability` (CanCanCan) in the live source for current default permissions.
4. Verify auth config in `config/initializers/devise.rb`.
5. For OAuth2, check Doorkeeper config in `config/initializers/doorkeeper.rb`.

## Conceptual Architecture

### Auth Stack

| Layer | Tool |
|-------|------|
| **Customer authentication** | Devise (v5 ships in-core; `spree_auth_devise` is **archived**) |
| **Admin authentication** | Same Devise system; differentiation via Spree::Role |
| **Authorization** | CanCanCan via `Spree::Ability` |
| **API auth (v3 Store)** | Publishable key + per-user JWT |
| **API auth (v3 Admin)** | Per-user API key + OAuth2 (Doorkeeper) |
| **API auth (v2 legacy)** | OAuth2 (Doorkeeper) password + client_credentials grants |
| **OAuth2 server** | Doorkeeper |
| **CSRF** | Rails default — required for HTML, exempted for API |

### `spree_auth_devise` Is Deprecated

The standalone `spree_auth_devise` gem is **archived as of Feb 2026**. Spree v5+ ships Devise auth in the core gem. Do NOT install the old gem on new projects — it conflicts and creates a maintenance burden.

### CanCanCan Permission Model

```ruby
# app/models/spree/ability.rb
class Spree::Ability
  include CanCan::Ability

  def initialize(user)
    user ||= Spree.user_class.new
    if user.has_spree_role?(:admin)
      can :manage, :all
    elsif user.has_spree_role?(:order_manager)
      can :manage, Spree::Order
    else
      can :read, [Spree::Product, Spree::Taxon]
    end
  end
end
```

Extend via decorator — don't replace.

### API Auth Tiers (v3)

| Token type | Scope | Where stored |
|------------|-------|--------------|
| **Publishable key** (`pk_…`) | Read-only public catalog + cart endpoints | Browser env (`NEXT_PUBLIC_*`) — safe |
| **User JWT** | Customer's account, their orders | **httpOnly cookie** server-side |
| **Cart token** (`order_token`) | Anonymous cart only | **httpOnly cookie** server-side |
| **Admin API key** | Per-user admin scope | Server env vars or vault — **never to browser** |
| **OAuth2 access token (admin scope)** | App integration | Server-to-server only |

### Webhook HMAC Verification

Webhooks 2.0 signs with HMAC-SHA256 over the raw body using a per-endpoint shared secret. Always verify:

```ruby
def verify(body, header, secret)
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, body)
  ActiveSupport::SecurityUtils.secure_compare(expected, header.sub(/^sha256=/, ''))
end
```

Use `secure_compare` — never `==` (timing attack).

### Rails OWASP Checklist for Spree

| Risk | Spree-specific mitigation |
|------|---------------------------|
| **A01 Broken access control** | CanCanCan `Spree::Ability` — never skip `authorize!`. Multi-store scoping. |
| **A02 Cryptographic failures** | Rails credentials. TLS everywhere. No secrets in YAML. |
| **A03 Injection** | ActiveRecord parameterization. Ransack: lock `ransackable_attributes`. |
| **A04 Insecure design** | Don't accept user-supplied IDs without ownership check. Use prefixed IDs. |
| **A05 Misconfiguration** | `config.force_ssl = true`. `config.action_controller.default_url_options = { protocol: 'https' }`. |
| **A06 Vulnerable deps** | `bundler-audit`, `brakeman` in CI. |
| **A07 ID&A failures** | Devise default rate-limits. Add 2FA via `devise-two-factor`. |
| **A08 Software integrity** | Pin gem versions. Verify gem signatures where supported. |
| **A09 Logging** | Filter params: `Rails.application.config.filter_parameters += [:password, :credit_card]`. |
| **A10 SSRF** | Never `Net::HTTP.get(params[:url])`. |

### Ransack Filter Exposure

API v2 filters use Ransack (`filter[status_eq]=complete`). By default, **all columns and associations are filterable**. Lock down per-model:

```ruby
# app/models/spree/order_decorator.rb
module SecureOrderRansack
  def self.prepended(base)
    base.class_eval do
      def self.ransackable_attributes(_auth = nil)
        %w[number state payment_state shipment_state created_at]
      end

      def self.ransackable_associations(_auth = nil)
        %w[user line_items]
      end
    end
  end
end
Spree::Order.prepend(SecureOrderRansack)
```

Without this, attackers can filter by sensitive columns (passwords, tokens).

### Prefixed IDs (v3)

API v3's prefixed IDs (`prod_…`, `ord_…`) are **opaque** strings — they don't expose row counts (sequential integers do). But always pair with **ownership checks**:

```ruby
order = current_store.orders.find(params[:id])  # scoped lookup
```

Not:
```ruby
order = Spree::Order.find(params[:id])  # IDOR risk
```

### PCI Scope

Spree's **never touches raw card numbers** when configured correctly:
- Stripe Elements / Adyen Drop-in / PayPal Buttons collect card data in the gateway's iframe
- Spree stores only the gateway's token (`tok_…`, `pm_…`, etc.)
- This keeps the merchant in **SAQ-A** scope, the lightest PCI tier

**Don't** build a custom card form that sends `params[:card_number]` to Rails. That escalates PCI scope dramatically.

### Multi-Store Data Isolation

As covered in `spree-multi-store`, always scope queries by `current_store`. A single missed scope can leak another tenant's orders.

### Devise Password Policies

Configure in `config/initializers/devise.rb`:

```ruby
config.password_length = 12..128
config.maximum_attempts = 5
config.unlock_in = 30.minutes
config.timeout_in = 30.minutes
```

Add zxcvbn-style strength via `devise_zxcvbn` if needed.

### 2FA

Spree doesn't ship 2FA, but Devise extensions add it:

```ruby
gem 'devise-two-factor'
```

Or use WebAuthn (`webauthn-ruby`) for hardware-key auth on admin accounts.

### Rails Master Key Hygiene

- **`RAILS_MASTER_KEY`** unlocks `config/credentials.yml.enc`
- Store it in your platform's secret manager (Heroku config, AWS Secrets Manager, etc.)
- **Never commit `config/master.key`** — gitignore it
- Rotate periodically: re-encrypt credentials with a new key, update env

### CSRF Exemption for APIs

```ruby
class Spree::Api::V3::BaseController < ActionController::API
  # ActionController::API doesn't include CSRF protection — correct for APIs
end
```

But **never disable CSRF on HTML controllers** (admin UI). Default Rails CSRF tokens protect admin from cross-origin attacks.

### Rate Limiting

Spree's per-endpoint API rate limiting is built in (v5+). For brute-force protection on admin login, layer **`rack-attack`**:

```ruby
# config/initializers/rack_attack.rb
Rack::Attack.throttle('admin_login', limit: 5, period: 15.minutes) do |req|
  req.ip if req.path == '/admin/login' && req.post?
end
```

## Implementation Guidance

### Pre-Production Security Checklist

1. **`config.force_ssl = true`** + HSTS headers
2. **`RAILS_MASTER_KEY` in vault** (not in code)
3. **Devise password policy** raised from defaults
4. **CanCanCan abilities** reviewed; no `can :manage, :all` outside admin role
5. **Ransack `ransackable_attributes` locked** per exposed model
6. **`bundler-audit` and `brakeman` clean** in CI
7. **Webhook signature verification** on every receiver
8. **OAuth2 application secrets rotated** quarterly
9. **API key issuance auditable** — Spree stores them; track in logs
10. **Multi-store scope test** — assert customer can't fetch other stores' orders
11. **PCI scope verified** — gateway iframes only; never custom card form
12. **Admin 2FA** for all admin users
13. **`config.filter_parameters`** includes `password`, `token`, `secret`, `credit_card`
14. **CSP headers** for the storefront (`Content-Security-Policy`)
15. **Backup encryption** at rest
16. **Image upload validation** — limit MIME types, sizes; scan for ImageMagick CVEs

### Brakeman in CI

```yaml
- run: bundle exec brakeman -q --no-pager --exit-on-warn
```

Treat new findings as build failures.

### Bundler-Audit

```bash
bundle exec bundler-audit check --update
```

Run on every CI build and weekly via scheduled job.

### Secrets in Decorators

```ruby
# Wrong — secret in code
ApiClient.new('sk_live_…')

# Right
ApiClient.new(Rails.application.credentials.dig(:my_service, :api_key))

# Also right
ApiClient.new(ENV.fetch('MY_SERVICE_API_KEY'))
```

### Common Security Pitfalls

- **Adding `spree_auth_devise` to a v5 project** — archived gem; conflicts.
- **Forgetting `current_store.orders` scoping** — leaks across stores.
- **Open Ransack filters** — exfiltration attack surface.
- **Custom credit card form** — escalates PCI scope.
- **Storing JWTs in `localStorage`** — XSS-exfiltratable.
- **Webhook handler without signature verification** — anyone can forge events.
- **Admin endpoints with `skip_before_action :authenticate_user!`** — accidental backdoor.
- **Mass assignment via permit!** — auditing the param permit list is essential.
- **Image processing on user-uploaded files without limits** — ImageMagick CVEs, memory exhaustion.
- **Sidekiq Web UI exposed without auth** — common production miss.

Always cross-reference the Spree security advisories page and `rubysec.com` for the gems your Spree version depends on.

Related Skills

woo-security

17
from OrcaQubits/agentic-commerce-skills-plugins

Implement WooCommerce security — nonces, capabilities, input sanitization, output escaping, data validation, PCI compliance considerations, and WordPress security best practices. Use when hardening a WooCommerce store or reviewing security posture.

webmcp-security

17
from OrcaQubits/agentic-commerce-skills-plugins

Implement WebMCP security best practices — permission model, data minimization, honest descriptions, input validation, fingerprinting prevention, and fraud mitigation. Use when auditing or hardening WebMCP tool implementations.

spree-upgrades

17
from OrcaQubits/agentic-commerce-skills-plugins

Upgrade a Spree application — version-to-version migration paths (v4 → v5 is a major rewrite; v5.x → v5.y are simpler), Gemfile pinning strategy, decorator brittleness across versions, deprecated gems to remove (`spree_auth_devise`, `deface`, `spree_backend`), API v2 → v3 migration via `spree_legacy_api_v2`, admin Bootstrap → Tailwind transition, the upgrade guide workflow, and rollback strategy. Use when planning a Spree upgrade or auditing technical debt blocking one.

spree-typescript-sdk

17
from OrcaQubits/agentic-commerce-skills-plugins

Build storefronts and integrations using `@spree/sdk` — the official TypeScript SDK for Spree's API v3 (v5.4+). Covers installation, the resource-builder pattern (`spree.products.list`, `spree.checkout.create`, etc.), Zod schema validation, server-only auth (httpOnly JWTs, never exposing API keys), error handling, typing customizations, and pinning SDK versions to backend Spree releases. Use when integrating Spree into a Next.js / Node.js / TypeScript project.

spree-testing

17
from OrcaQubits/agentic-commerce-skills-plugins

Test Spree applications and extensions with RSpec — `spree_dev_tools` gem (v5.2+) for factories and helpers, FactoryBot patterns (prefer `build` over `create`), Capybara feature specs, controller/request specs, testing decorators and subscribers, dummy app for extension testing, system specs for the Hotwire admin, and CI patterns. Use when writing Spree tests, setting up CI, or refactoring slow specs.

spree-shipping-fulfillment

17
from OrcaQubits/agentic-commerce-skills-plugins

Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.

spree-setup

17
from OrcaQubits/agentic-commerce-skills-plugins

Bootstrap a new Spree project — `create-spree-app` CLI (v5.2+), `spree-starter` Rails backend, the Next.js storefront repo, `bin/rails g spree:install`, sample data, Docker Compose, and the PostgreSQL + Redis + Sidekiq prerequisites. Use when starting a new Spree project from scratch or onboarding an existing repo.

spree-promotions

17
from OrcaQubits/agentic-commerce-skills-plugins

Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.

spree-performance

17
from OrcaQubits/agentic-commerce-skills-plugins

Profile and optimize a Spree application — N+1 queries with bullet/scout, database indexing strategy for Spree's polymorphic associations, Rails fragment + Russian doll caching, ActiveStorage variant pre-generation, Sidekiq queue tuning, MeiliSearch vs Postgres FTS tradeoffs, Puma worker/thread sizing, CDN strategy for catalog pages, asset precompile time, and load testing. Use when Spree is slow, the database is hot, or you're preparing for a traffic spike (Black Friday, launch).

spree-payments

17
from OrcaQubits/agentic-commerce-skills-plugins

Integrate payment gateways with Spree — PaymentMethod model, the v5.4+ PaymentSession provider-agnostic checkout flow, Stripe via `spree_stripe` (Apple/Google Pay, Link, Connect for marketplaces), Adyen via `spree_adyen`, PayPal via `spree_paypal_checkout`, StoreCredit / GiftCard as payment methods, refunds, payment state machine, and authoring a custom gateway. Use when wiring a payment integration, handling webhooks from a gateway, or debugging payment-state issues.

spree-multi-store

17
from OrcaQubits/agentic-commerce-skills-plugins

Configure Spree for multi-store and multi-region commerce — one Rails install running many `Store` records, the v5.4+ `Market` model (currency + locale + payment methods + shipping per region), what's shared vs per-store (products+inventory+customers shared; orders+payments+themes per-store), the Marketplace module (Enterprise — vendors/commission/payouts via Stripe Connect), and the Multi-tenant SaaS model. Use when planning a multi-brand or multi-region Spree deployment.

spree-legacy-api-v2

17
from OrcaQubits/agentic-commerce-skills-plugins

Work with Spree's legacy v2 APIs — JSON:API-style Storefront API at `/api/v2/storefront/*` and Platform API at `/api/v2/platform/*`, Doorkeeper OAuth2 (password grant for storefront, client_credentials + admin scope for platform), the `spree_legacy_api_v2` gem (required in v5+ for backward compatibility), and migration patterns to API v3. Use when maintaining a v2 client during a migration window, integrating an older partner system, or deciding when to cut over to v3.