# Launch Playbook

> Evidence-based pre-launch security playbook for MVPs and SaaS. Scans the real repo for open DBs/RLS gaps, IDOR, secret leaks, auth enumeration, AI cost abuse and prompt injection, uploads/SSRF, rate limits, CORS, and unsafe production config. Outputs SHIP / SHIP WITH FIXES / DO NOT SHIP with file:line proof. Use when the user says launch-playbook, launch playbook, pre-launch security, secure my launch, is this safe to ship, security checklist, audit before deploy, RLS check, vibe coding security, or runs /launch-playbook or /secure-launch.

- Skill: `0xkaizoku/launch-playbook` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add 0xkaizoku/launch-playbook`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0xkaizoku/launch-playbook/raw
- Safety review: pending (external: skill-scanner FAIL, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: 0xkaizoku (https://skillmd.com/u/0xkaizoku)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/0xkaizoku/launch-playbook

---


# launch-playbook — Pre-Launch Security Playbook

Run a **time-boxed, evidence-based** security playbook before public launch.

**Agent-agnostic procedure.** Same steps on Claude Code, Gemini CLI, OpenAI Codex/ChatGPT agents, Cursor, Windsurf, Grok, or a human with a terminal. Loading differs by host (see README); execution does not.

| | |
|--|--|
| **Goal** | Baseline that stops the failures fast-shipped apps actually hit |
| **Not a goal** | Enterprise audit, pentest cert, HIPAA/PCI/SOC2 |
| **Budget** | ~30 minutes of agent work; depth over theater |
| **Rule** | No finding without path/evidence. No “might be vulnerable” without a reason. |

Load when needed (paths relative to this skill folder):
- `references/checklist.md` — human checklist
- `references/scan-playbook.md` — commands + secret patterns
- `references/ai-prompts.md` — optional deep-dive prompts

If invoked without skill auto-load: user may attach or `@` this file. Follow it fully anyway.

---

## Modes

| User intent | Mode |
|-------------|------|
| Default / “audit” / “checklist” | **Report only** |
| “Fix it” / “make it shippable” | **Remediate** then re-score |
| “Quick” / “10 min” | **Fast path**: secrets → access control → paid APIs → auth → stop |

Do not rotate production secrets, wipe data, or force-enable RLS that locks out prod without explicit confirmation.

---

## Phase 0 — Detect stack (2 min, mandatory)

Inspect lockfiles, config, and imports. Record in the report:

```text
Framework: Next.js | Remix | SvelteKit | Vite SPA | other
Auth: Supabase Auth | Clerk | Auth.js/NextAuth | Lucia | Firebase | custom | none
DB: Supabase | Firebase | Prisma+Postgres | Mongo | Drizzle | other | none
Hosting signals: Vercel | Cloudflare | Fly | Railway | unknown
Paid APIs: OpenAI | Anthropic | xAI | Stripe | Resend | Twilio | other
Client data access: direct Supabase/Firebase from browser? yes/no
AI features: chat | agents/tools | image | embeddings | none
Uploads: yes/no   Payments: yes/no   Multi-tenant: yes/no
```

**Branching:** Skip N/A sections. Map Supabase RLS checks to Firebase Rules / server-only Prisma authz. Never pretend a Next-only check applies to a static SPA.

---

## Phase 1 — Mandatory scans (run these, do not “reason about” secrets)

Use ripgrep/grep tools available. Prefer `references/scan-playbook.md`. Minimum:

### 1.1 Secret & key exposure
Search source (exclude `node_modules`, `.git`, lockfiles, `dist` / `.next` / `build` if noisy):

| Pattern family | Examples |
|----------------|----------|
| Cloud LLM | `sk-` (OpenAI), `sk-ant-`, `xai-`, `gsk_` (Groq), `AIza` (Google) |
| Stripe | `sk_live`, `sk_test`, `rk_live`, `whsec_` |
| Supabase | `service_role`, long JWT-looking `eyJ...` in client files |
| AWS | `AKIA` + secret key patterns, `AWS_SECRET` |
| GitHub | `ghp_`, `github_pat_`, `gho_`, `ghu_` |
| Private keys | `BEGIN PRIVATE KEY`, `BEGIN RSA PRIVATE KEY`, `BEGIN OPENSSH` |
| Slack/Discord | `xoxb-`, `xoxp-`, `xoxa-`, Discord bot tokens |
| Generic | `api[_-]?key`, `secret[_-]?key`, `password\s*=` in non-test code |

Also check:
- `.env`, `.env.local`, `.env.production` **tracked by git**?
- Client env misuse: `NEXT_PUBLIC_*` / `VITE_*` / `EXPO_PUBLIC_*` / `PUBLIC_*` holding secrets
- README/docs/screenshots with real keys
- `service_role` or `DATABASE_URL` imported from modules that also run in the browser
- CI/CD config and committed Terraform/Pulumi secrets

Classify each hit: **public-by-design** | **secret-exposed** | **false positive**.

`secret-exposed` in client or git → **DO NOT SHIP** until rotated + removed from tree (history purge is a follow-up; rotation is immediate).

### 1.2 Dangerous code patterns
Search and open matches:

| Risk | Patterns / files |
|------|------------------|
| XSS | `dangerouslySetInnerHTML`, `{@html`, `v-html`, unescaped markdown→HTML |
| SQL concat | string-built queries, raw `$queryRaw` / `$executeRaw` with user input |
| Command inject | `exec(`, `execSync(`, `spawn(` with user-controlled strings |
| SSRF | `fetch(user`, `axios.get(req.`, webhook/URL fields without allowlist |
| Open redirect | `redirect(`, `window.location` = user param without allowlist |
| Admin naked | `/admin`, `/debug`, `/api/test`, swagger/openapi in prod without auth |
| Mass assign | spreading `req.body` straight into Prisma `data:` / Supabase update |
| Debug prod | missing production guards, `console.log` of tokens, source maps forced on |

### 1.3 Dependency & config
- Lockfile present? (`package-lock.json` / `pnpm-lock.yaml` / `yarn.lock` / `bun.lock`)
- Run `npm audit --omit=dev` (or pnpm/yarn equivalent) when Node project; note **critical/high only**
- `.gitignore` includes `.env*` (and secrets are not force-added)
- Public storage: Supabase storage policies, S3 public ACLs, unauthenticated upload routes

---

## Phase 2 — Access control & data plane

### 2.1 Database / backend access (highest failure rate)

**Supabase**
- [ ] RLS enabled on every table with user/tenant data
- [ ] Policies scope by `auth.uid()` or org membership — not `using (true)` for private data
- [ ] `WITH CHECK` on inserts/updates (not only `USING`)
- [ ] No browser use of service role
- [ ] Migrations / SQL in repo match “we have RLS” claims; empty policy list on private tables is FAIL

**Firebase**
- [ ] Rules default-deny; read/write gated by `auth.uid` + resource ownership
- [ ] No open `allow read, write: if true` (or `if request.auth != null` alone on private docs)

**Prisma / Drizzle / server DB**
- [ ] No `DATABASE_URL` in client bundle
- [ ] Every query that touches user rows filters by session user/org
- [ ] No “getById” without ownership check (classic IDOR)

**IDOR / multi-tenant**
- [ ] Routes like `/api/orders/[id]`, `/api/docs/[id]` verify **owner or membership**, not just “is logged in”
- [ ] Numeric/sequential IDs: confirm server rejects cross-user access in code
- [ ] Team/org features: membership checked on mutate, not only on UI hide
- [ ] Realtime / subscriptions / storage paths also scoped (not only REST handlers)

**False confidence:** RLS enabled with wrong policies is worse than obvious “no RLS” (silent “secure”). Flag always-true policies for authenticated role on sensitive tables.

### 2.2 Validation & errors
- [ ] Writes validated **on the server** (Zod/Valibot in Server Actions / API / Edge) — client-only validation = FAIL/WARN
- [ ] Length limits on text fields; file size/MIME if uploads
- [ ] Production errors generic; no SQL/stack/`PrismaClientKnownRequestError` to clients
- [ ] Full user rows (password hash, tokens, internal flags) not returned to client

### 2.3 Uploads & storage (if present)
- [ ] Auth required unless intentionally public
- [ ] Size + type allowlist; no SVG-as-image without sanitization if served inline
- [ ] Stored outside web root or via signed URLs
- [ ] Path not user-controlled (`../../`)
- [ ] Bucket not world-readable for private user content
- [ ] Filename not used as executable path; content-type not fully trusted from client

### 2.4 SSRF & outbound fetch (if present)
- [ ] User-supplied URLs allowlisted (scheme + host) or blocked for link-local / metadata IPs (`169.254.169.254`, `metadata.google.internal`, `127.0.0.0/8`, private RFC1918 ranges)
- [ ] No blind `fetch(req.body.url)` for “preview” / “import” features without controls
- [ ] Redirect-following considered (open redirect to internal host)

---

## Phase 3 — Auth & sessions

### 3.1 Failure-case design (code + copy review)

| Test | Secure | Insecure |
|------|--------|----------|
| Bad password ×5 | Generic error; rate limit / lockout | “Wrong password for this email”; no throttle |
| Reset unknown email | Same response as known | “Email not found” |
| Verify/magic link twice | Expired/used message | 500 / infinite re-auth bugs |
| Signup existing email | Non-enumerating message (or intentional product choice documented) | Clear “already registered” with no rate limit |

Also:
- [ ] Session cookies: `HttpOnly`, `Secure`, `SameSite` (or provider defaults verified)
- [ ] Logout invalidates server session when applicable
- [ ] Password reset tokens single-use / short TTL if custom
- [ ] OAuth: redirect URI allowlist; no open `redirectTo` / `callbackUrl` / `next` from query
- [ ] Email verification required if app holds sensitive data (else WARN)
- [ ] CSRF: cookie-session mutations protected (SameSite + origin checks / framework CSRF)

### 3.2 Authorization ≠ authentication
Logged-in is not enough. Admin/role checks must be **server-side** on privileged routes. Client-only role flags in localStorage/JWT claims without server verify = FAIL.

---

## Phase 4 — AI features (skip if none)

Fast-shipped AI apps die on **cost** and **prompt injection**, not only key leaks.

- [ ] Model calls **only** on server; keys never in client
- [ ] **Auth** required for expensive routes (or hard public quota + CAPTCHA)
- [ ] **Rate limit** + **per-user daily/monthly cap** on generation endpoints
- [ ] Provider dashboard hard cap + billing alert recommended in report
- [ ] User content treated as **untrusted**: not concatenated into system prompts as instructions
- [ ] Tool/agent calling: tools cannot exfiltrate secrets, run shell, or hit internal URLs without allowlist
- [ ] Output rendering safe (markdown XSS / HTML)
- [ ] No logging full prompts that contain user PII to third-party log drains without need
- [ ] Streaming endpoints cannot be invoked anonymously in a tight loop without limits
- [ ] Max tokens / max steps bounded on agent loops

**DO NOT SHIP** if an unauthenticated endpoint can burn paid tokens at scale.

---

## Phase 5 — Abuse, cost, and edge

- [ ] Rate limits: auth routes + any paid/third-party route
- [ ] CAPTCHA/bot protection on public signup/contact/waitlist (Turnstile, hCaptcha, etc.)
- [ ] CORS: explicit origins in prod; not `*` with credentials on private APIs
- [ ] Webhooks (Stripe etc.): signature verify; reject unsigned; idempotency considered
- [ ] Body size limits on APIs
- [ ] Security headers where framework allows (baseline CSP, `X-Content-Type-Options: nosniff`, frame controls if sensitive UI)
- [ ] Cron/admin secrets not guessable query tokens
- [ ] Feature flags / debug routes disabled or auth-gated in production

---

## Phase 6 — Legal / product floor (light, code-aware)

- [ ] Privacy policy route or external link exists if you collect email/PII
- [ ] Password hashing if custom auth (`bcrypt` / `argon2` / `scrypt` — not plain, not reversible “encrypt password”)
- [ ] No debug scripts that export users or dump production data into personal accounts
- [ ] Data map one-liner in report: where PII lives + processors (auth, email, analytics, AI)

Keep legal notes short and product-relevant. If AI-generated code or third-party licenses matter for *this* ship, one line: human review + license hygiene. Do not expand into caselaw.

---

## Phase 7 — Verdict

### Severity → verdict

| Severity | Examples | Effect |
|----------|----------|--------|
| **S0 Blocker** | Secret in client/git; no RLS/rules on private data; unauth paid AI burn; public service_role | **DO NOT SHIP** |
| **S1 High** | Clear IDOR; SSRF; stored XSS; admin open; webhook unsigned with money impact | **DO NOT SHIP** if exploitable without auth **or** affects all tenants/users |
| **S2 Medium** | Auth enumeration; missing rate limit on auth; client-only validation; no CAPTCHA on spammy forms | **SHIP WITH FIXES** |
| **S3 Low** | Missing CSP; verbose logs server-only; dependency moderate noise | WARN; ship ok |

**SHIP** only if zero S0/S1 open.

If S1 is authenticated-only and single-tenant scoped, still default to **DO NOT SHIP** unless the user explicitly accepts residual risk in writing in the report.

### Report format (always)

```markdown
# Launch Playbook Report

**Repo:** …
**Stack:** … (from Phase 0)
**Mode:** report | remediate | fast
**Verdict:** SHIP | SHIP WITH FIXES | DO NOT SHIP

## Scorecard
| Phase | Area | Status | Highest severity |
|-------|------|--------|------------------|
| 1 | Secrets & dangerous patterns | PASS/FAIL/WARN/N/A | S0–S3 |
| 2 | Access control / IDOR / uploads | | |
| 3 | Auth & sessions | | |
| 4 | AI features | | |
| 5 | Abuse / cost / edge | | |
| 6 | Legal / product floor | | |

## Blockers (S0–S1)
### B1 — title
- **Evidence:** `path:line` — snippet or fact
- **Impact:** …
- **Fix:** concrete change (code/SQL/config)

## Fixes before / within 48h (S2)
…

## Passed (evidence-backed)
- …

## Scans run
- commands + outcome summary

## Out of scope / N/A
- …
```

Keep the report **short**. Prefer 5 real blockers over 40 theoretical essays.

---

## Remediate mode

Order of operations:
1. Remove/move secrets; tell user to **rotate** (do not print full secrets in chat)
2. Access control (RLS/rules/ownership checks)
3. Close unauth paid endpoints (auth + rate limit + cap)
4. Fix XSS/SSRF/open redirect with minimal patches
5. Generic errors + server validation on hottest write paths
6. Re-run Phase 1 scans + re-score

---

## Anti-slop rules (mandatory)

1. **Evidence or delete the finding.** No “consider adding…” without a gap you observed.
2. **Do not dump `references/ai-prompts.md` as the whole audit.** Use those prompts only as optional depth after mandatory scans.
3. **Do not mark PASS** because the README says “secure” or “read-only.”
4. **Map to stack.** Wrong-stack advice is a failed audit.
5. **Prioritize money and data loss** over header perfectionism.
6. **Never claim** “pentest complete” or “compliant.”
7. If the app is static marketing with no backend: short report, mostly N/A, still scan for leaked keys in repo.

