Skip to main content

audit-auth-flows

Read-only audit of app-layer auth: route×gate matrix, session lifecycle, OAuth, provider traps (getSession vs getUser, middleware-only gates, CVE-2025-29927). Use when "audit our auth" or "check middleware protection". RLS → plan-rls-audit.

Quellinformationen

Repository
kensaurus/skills
Letzte Quellaktivität
23. September 2026 um 10:42
Erkannte Sprache von SKILL.md
Englisch
Sterne
8
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
audit-auth-flows
description
Read-only audit of app-layer auth: route×gate matrix, session lifecycle, OAuth, provider traps (getSession vs getUser, middleware-only gates, CVE-2025-29927). Use when "audit our auth" or "check middleware protection". RLS → plan-rls-audit.
license
MIT
effort
high
# audit-auth-flows — Middleware is edge routing, not a security boundary **Degree of freedom: MIXED — declared per phase** - Phases 0–4 (discovery, judgment): **[HIGH freedom]** — reason about what you find; checklists prompt investigation, they are not tick-scripts. - Phase 5 (live probes) and the getSession grep: **[LOW freedom — run exactly]** — a skipped probe is an unproven claim, not a saved step. Read-only. Map the auth surface and prove, route by route, that identity is verified where it matters. Then stop. Hand fixes to a follow-up session. **2026 consensus (official, post CVE-2025-29927):** Next.js middleware runs before cache and routing. Vercel: *"We do not recommend Middleware to be the sole method of protecting routes in your application."* An audit that only reads `middleware.ts` and declares the app safe has missed the point. Defense in depth: edge + route handler / server action + data layer. Each layer covers a different part of the request path. ## How to reason in this audit Every finding is recorded in this shape, in this order: 1. **Observe** — what does the code/probe actually show? (quote the line or the response) 2. **Interpret** — what does that mean for who can reach what? (don't assume intent) 3. **Classify** — real exposure / defense-in-depth-gap / correct-as-is / needs-a-probe 4. **Severity** — by blast radius: unauth data access & auth bypass = critical; everything else justified against what an attacker actually gains. If you cannot complete Interpret from static code, it is a **needs-a-probe** item for Phase 5, not a conclusion. ## Worked example > **Observe:** `middleware.ts` matcher is `['/dashboard/:path*']`. The route > `/api/orders/[id]` has no check in the handler. > **Interpret:** API routes are not under the matcher, so middleware never > runs on them; the order endpoint is reachable without auth. Layout-level > checks don't cover direct API calls. > **Classify:** real exposure (not merely a defense-in-depth gap — data is > actually reachable). > **Severity:** critical — unauthenticated data access. > **Finding row:** `/api/orders/[id]` | edge+route | critical | matcher > excludes /api; handler has no `getUser()` check | probe P5-a confirmed > 200 while logged out. ## This skill vs neighbors | Skill | Owns | |---|---| | **audit-auth-flows** (this) | Session lifecycle, route×gate matrix, OAuth, provider traps | | `audit-security` | OWASP checklist (injection, headers, deps) — may fix inline | | `plan-security-audit` | Plan-only hardening burndown | | `plan-rls-audit` | Data-layer who-can-read-which-rows | | `plan-secrets-audit` | Where `service_role` / keys live | | `plan-privacy-compliance` | Consent / policy vs collection — not session gates | | `backend-patterns` | Building the middleware / guard fixes after this audit | | `test-exploratory` | Live guest vs logged-in probe (runtime complement to this static matrix) | --- ## Phase 0 — Detect the auth stack [HIGH freedom] Identify: provider (Supabase Auth / NextAuth / Clerk / Auth0 / custom), framework protection (Next.js middleware + `matcher`, route guards, server-action checks), session transport (cookie vs `localStorage` vs `Authorization` header), token model (JWT vs server session). Enumerate pages, API routes, server actions, edge functions, admin areas. Record the pinned framework version — auth CVEs are version-gated. --- ## Phase 1 — Route × gate coverage matrix [HIGH freedom — the centerpiece] Every protected route / action, one row. Apply the reasoning chain per row. | route/action | intended access | gate that protects it | verified server-side? | data-layer backstop? | Hunt the gaps this exposes: - **Matcher holes** — `config.matcher` misses a protected path. API routes and server actions often bypass page middleware — each needs its own check. - **Layout-only protection** — a layout/wrapper is a client-side illusion. It does not gate data fetches or direct API/action calls. - **Middleware-as-only-gate** — the CVE-2025-29927 class. Any route where middleware is the *sole* authorization layer is a finding **regardless of framework version or host**. Patching the CVE is not defense in depth. - **Server action exposure** — Next.js server actions are POST endpoints callable directly. Each mutating action must check identity itself. - **Unlisted routes** — grep the router; do not trust the matcher list. --- ## Phase 2 — Provider and framework traps [HIGH freedom, except the grep = LOW] **Supabase (check first on this stack):** - **[LOW freedom — run exactly]** `grep -rn "getSession(" <server-code-paths>`. Treat every server-side hit as a finding until proven client-side-only. Official JS docs: cookie/storage values *may not be authentic*; `getSession()` is strongly advised against for authorization. `getUser()` performs a network request to the Auth server and *can* be used to base authorization rules. Newer SSR guides also use `getClaims()` to *refresh* cookies — that is not a substitute for verifying identity at the handler. - Service-role key on any client-exposed path → `plan-secrets-audit`. RLS assumed but not enforcing → `plan-rls-audit` (data-layer backstop; this skill is the app layer above it). - `autoRefreshToken` disabled without manual refresh; JWT expiry absurdly short (<300s) hammering refresh. **Next.js CVE-2025-29927** (GHSA-f82v-jwr5-mffw, critical): bypass authorization if the check occurs only in middleware, via the internal `x-middleware-subrequest` header. **Patched:** 12.3.5 / 13.5.9 / 14.2.25 / 15.2.3. **Impacted:** self-hosted `next start` and `output: 'standalone'`. Vercel-hosted apps were incidentally protected; Netlify and Cloudflare Workers were not affected. Workaround if unpatched: drop `x-middleware-subrequest` before it hits Next. If below the fix → P0 *and* a defense-in-depth finding. Also sweep later Next.js auth CVEs against the pinned version — query the GitHub advisory database rather than listing CVEs from memory; 29927 is not the only one. **General rule:** any "the framework enforces auth at the edge" assumption is a finding, whatever the framework. **NextAuth / Clerk / Auth0** — session strategy (JWT vs database), callback / redirect validation, and whether `auth()` / session checks actually run server-side per protected operation. --- ## Phase 3 — Session lifecycle [HIGH freedom] **Cookies** — `HttpOnly`, `Secure`, `SameSite` (Lax min, Strict for sensitive); scoped path/domain; not readable by JS. Token in `localStorage` is XSS-exfiltratable — flag it; CSRF-vs-XSS must be a deliberate choice. **Logout that actually logs out** — a stateless JWT stays valid until expiry if logout only clears the client. Confirm server-side invalidation (Supabase `signOut` revokes the refresh token). Probe it (Phase 5). Official sessions FAQ: access tokens can still be presented after sign-out unless the app also checks revocation / uses `getUser()`. **Rotation & timeouts** — rotate on privilege change (login, role elevation) to kill fixation; idle timeout and absolute-max lifetime both present. Current norm: short access tokens, longer **rotated** refresh tokens with reuse detection, capped absolute session. **Refresh-token rotation + reuse detection** — official Supabase sessions docs: a refresh token is single-use, with two exceptions (default **10s reuse interval** — do not widen; parent-token retry for lost responses). Outside those exceptions the **whole session family is revoked**. Disabling that in Advanced Settings is "generally not recommended" — flag if widened or disabled. --- ## Phase 4 — Credentials, OAuth, authorization [HIGH freedom] **OAuth / OIDC** — `state` (CSRF on the callback), PKCE for public clients, redirect-URI allowlist exact-match, provider `email_verified` checked before trust, account-linking collisions handled. **Credential flows** — reset tokens single-use + expiring + not leaked in URL/referrer; **user enumeration** via differing error text or timing on login/reset/signup; rate limiting on auth endpoints specifically; MFA state cannot be skipped mid-flow. **Authorization above the data layer** — not just "is logged in" but "is allowed"; IDOR on API params (`/api/orders/:id` scoped to caller?); admin routes gated by role server-side. Name which layer each control lives in. `plan-rls-audit` owns the data-layer half. --- ## Phase 5 — Live probes [LOW freedom — run each, in order, non-prod] Force the failures rather than only reading code. Log each as `P5-x | expected | actual | evidence`: - **P5-a** Hit each protected route / API / action **unauthenticated** → expect deny (not a redirect that still returns data). - **P5-b** Replay a **logged-out** session token → expect reject. - **P5-c** Tamper the session cookie / swap another user's identifier → reject. - **P5-d** IDOR: as user A, request user B's object by id → deny. - **P5-e** If Next.js is version-vulnerable or self-hosted: send `x-middleware-subrequest` and confirm WAF/edge *and* a route-handler check still stop it. - **P5-f** User-enumeration: compare wrong-password vs nonexistent-user (text + timing). Do not run these against production. Do not write exploit PoCs into the repo. --- ## Self-critique before reporting [LOW freedom — do not skip] Challenge every finding against this rubric; drop or downgrade any that fail: 1. **Evidenced, not assumed** — quoted line or probe result? If it rests on "the code probably…", mark unconfirmed or run the probe. 2. **Reproducible** — did the probe succeed twice, or once? Flaky ≠ finding. 3. **Severity justified** — "critical" requires actual unauth access or bypass, not theoretical risk. 4. **Right layer** — app-layer (yours) or data-layer (→ `plan-rls-audit`)? 5. **No false safety** — did you confirm a gate *works*, or only that it *exists*? A check a probe bypassed is critical, not a pass. --- ## Definition of Done - [ ] Auth stack + framework version recorded - [ ] Degree-of-freedom respected: Phase 5 probes and the getSession grep run exactly, in order - [ ] Route×gate matrix complete; every row reasoned Observe→Interpret→Classify→Severity - [ ] Matcher holes, layout-only, middleware-as-sole-gate, unprotected server actions surfaced - [ ] `getSession()`-for-authz grepped; `getUser()` confirmed on every server gate - [ ] CVE-2025-29927 (and later auth CVEs) checked against the pinned version - [ ] Cookie flags, localStorage exposure, CSRF pairing assessed - [ ] Server-side logout invalidation confirmed by probe; rotation + idle/absolute timeouts present - [ ] Refresh reuse interval not widened; family revocation not disabled - [ ] OAuth state / PKCE / redirect-allowlist / email_verified checked - [ ] User-enumeration, reset tokens, auth-endpoint rate limits, MFA-skip checked - [ ] App-layer authorization (roles, IDOR, admin) split from the RLS half - [ ] Live probes run non-prod; each logged pass/fail - [ ] Self-critique rubric applied; unevidenced/flaky findings dropped or downgraded - [ ] Read-only — findings presented, not patched ## Output format 1. **Route × gate matrix** — the table, gaps highlighted 2. **Findings** — issue | layer (edge/route/data) | severity (critical = unauth access / auth bypass) | file:line or probe | fix shape — each finding in the worked-example shape 3. **Provider-trap list** — exact call to change (`getSession` → `getUser`, etc.) 4. **Probe log** — P5-x | expected | actual | evidence 5. **Fix plan** — auth-bypass and getSession-authz first, then session lifecycle, then enumeration/OAuth. Handoffs: `plan-rls-audit`, `plan-secrets-audit`, `plan-security-audit`, `backend-patterns`. Present and stop.
Auf GitHub ansehen