Security model for DreamBees Art — open-source self-hosted ecommerce on Firebase + Stripe.
Root policy file: SECURITY.md (summary + links).
| Principle | Implementation |
|---|---|
| Server-side commerce | Money and stock mutations only in API routes → Core protocols |
| No client Firestore writes | Security rules block direct commerce mutation from browsers |
| Signed sessions | HTTP-only cookies; SESSION_SECRET HMAC |
| CSRF mitigation | assertTrustedMutationOrigin on mutating routes |
| Least privilege admin | Role + elevation for refunds and destructive ops |
| Idempotent mutations | Safe retries without double charge/refund/stock |
| Secret | Purpose |
|---|---|
SESSION_SECRET |
Session cookie signing |
FIREBASE_SERVICE_ACCOUNT_JSON |
Firestore Admin SDK |
STRIPE_SECRET_KEY |
Payment API |
STRIPE_WEBHOOK_SECRET |
Webhook signature verification |
SYSTEM_JOB_TOKEN |
Cleanup cron bearer auth |
| Variable | Notes |
|---|---|
NEXT_PUBLIC_FIREBASE_* |
Firebase client SDK — expected public |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Stripe.js — expected public |
NEXT_PUBLIC_SITE_URL |
Canonical URL |
| Secret | Purpose |
|---|---|
BREVO_API_KEY |
|
GEMINI_API_KEY / HERMES_* |
Concierge |
CHECKOUT_ENDPOINT + related |
Trusted checkout gateway |
| Environment | Where |
|---|---|
| Local dev | .env (gitignored if contains secrets — use .env.example as template only) |
| Production | Firebase/hosting secret manager or CI secrets — never in repo |
Never commit: live API keys, service account JSON, webhook secrets, production SESSION_SECRET.
| Surface | Mechanism |
|---|---|
| Customer | Firebase Auth → signed session cookie |
| Admin | Same session + users.role === 'admin' |
| Elevated ops | Step-up / elevation flag on admin session |
| System jobs | Bearer SYSTEM_JOB_TOKEN |
| Stripe webhooks | Stripe signature header |
Guards: src/infrastructure/server/apiGuards.ts
| Function | Use |
|---|---|
requireSessionUser |
Authenticated customer |
requireAdminSession |
Admin routes |
requireStepUpAdminSession |
High-risk admin |
requireConfiguredBearerToken |
System cleanup routes |
Request
→ session valid?
→ admin role? (if /api/admin/*)
→ elevated? (if refund / sensitive)
→ same-origin? (if mutation)
→ rate limit ok?
→ protocol delegate (business authorization inside AdminFlowService)
Concierge destructive tools add validateToolCall + session policy + amount caps.
| Risk | Mitigation |
|---|---|
| Double charge | Stripe idempotency + webhook event dedup |
| Double refund | refund_execution_claims + processedRefundKeys |
| Double stock move | Ledger idempotency markers |
| Route bypassing protocols | Verification ladder seal tests in CI |
| Admin refund abuse | Elevation + reason + operator event log |
| Concierge over-refund | MAX_CONCIERGE_REFUND_CENTS + auth required |
Frozen boundary: commerce-protocol-frozen.md
Rules must enforce:
- Customers read own orders/cart
- No client writes to orders, ledger, protocol claim collections
- Admin operations go through Admin SDK on server
Expected behavior tested in src/tests/firestore-security.test.ts — deploy rules that pass these tests.
| Requirement | Notes |
|---|---|
| HTTPS in production | Required for secure cookies |
| HTTP-only cookies | Session theft mitigation |
| Same-origin mutations | CSRF |
Firebase Hosting provides HTTPS by default.
Applied on sensitive routes (auth, checkout, concierge) via assertRateLimit — backed by Firestore rate_limits collection.
| Log | Contents |
|---|---|
AuditService |
Operator/customer actions |
admin_operator_events |
Admin protocol mutations |
refund_execution_events |
Refund audit with source |
| Structured logger | orderId, caseId, stripeEventId |
Admin UI: /admin/audit
If secrets exposed:
- Rotate immediately — Stripe keys, webhook secret,
SESSION_SECRET, Firebase service account - Invalidate active sessions (session secret rotation forces re-login)
- Review Stripe Dashboard for unauthorized charges/refunds
- Scan git history for committed secrets
- Update hosting env vars before redeploy
- Unique production
SESSION_SECRET - Firebase service account scoped minimally
- Firestore rules deployed and tested
- Stripe webhook secret matches live endpoint only
-
SYSTEM_JOB_TOKENstrong random; not in client - Default dev admin password removed
-
ALLOW_PRODUCTION_SEEDING=false - No secrets in logs or error responses
- HTTPS only
- Dependency audit (
npm audit) in CI