Boring Docs

Security — IDOR + OWASP-10 Lockdown

Ownership bindings, allowlists, verification, headers, deps.

Security

Threat model: solo-founder VPS behind nginx/boring.conf, PSP webhooks exposed, SQLite default. Deny by default.

A01 Broken Access Control (IDOR)

  • Mutating routes scope by session: board {_id,userId}, post via board ownership, product owner-or-admin with 404 masking, sync-dodo sellerId match, user self-only.
  • assertActorIntegrity(session, body) in libs/security.js: automatically verifies that client-provided IDs (userId, authorId, sellerId) match session.user.id.
  • Cancel subscription binds subscriptionId → order buyer/customer or admin (403 else).
  • Claim-repo binds to authenticated user only; githubUsername regex enforced.
  • Purchases/billing merge id+email orders with dedupe; never trust client-supplied owner ids.

A02 Crypto / A07 Auth

  • NextAuth JWT + per-DB adapters; NEXTAUTH_SECRET required; dev login dev-only; stale sessions → /auth without loops; ADMIN_EMAILS fail-closed in prod.

A03 Injection

  • SQL via prepared statements only (better-sqlite3, Supabase client, Mongoose). No string-built queries.
  • Markdown → HTML is the one XSS sink (dangerouslySetInnerHTML): repo-owned only; sanitize if opened. React escapes everything else.

A04/A05 Design + Misconfig

  • Webhooks verify (Stripe SDK, Dodo Standard Webhooks + replay window, LS HMAC) and return 4xx/500 correctly for retry.
  • Redirects must be relative same-origin: auth callbackUrl, portal returnUrl, checkout successUrl/cancelUrl. Enforced via safeUrl(url, fallback) in libs/security.js.
  • In-memory sliding-window rateLimit(key, limit, windowMs) for lightweight route-level abuse prevention.
  • Headers in next.config.js + nginx (DENY/nosniff/referrer/permissions); nginx also rate-limits /api/auth + /api, denies dotfiles/env/db.

A06/A08/A09/A10

  • Deps: run npm audit, upgrade flagged (axios/form-data/eslint chain), retest. No new packages without need.
  • Integrity: prices from product row; webhooks dedupe on providerPaymentRef (unique/sparse everywhere).
  • Logging & Incident Response: scream(message, context) in libs/telemetry.js fires emergency alerts to Telegram/Discord with a 60-second circuit breaker.
  • SSRF: only fixed-host fetches (GitHub API, PSPs); repo from env, username encoded.