# 09 — SECURITY MONITORING (live-verification methodology) Generated 2026-07-05 · prod arm64-5ee0ae12002f · regenerated after wipe > snapshot 2026-07-05 · for an external security monitor (GPT Pro / Codex). How to prove a fix live, which endpoints to poke, expected codes, and how to demand proof. Pairs with 06 (threat posture) and 05 (route→auth). ## Controls in place (what to expect) | Control | Where | Behaviour | |---|---|---| | Session auth | NextAuth v5 `auth()` per-route | unauth on a session route → **401/403** (valid-shape). | | Admin gate | `requireAdmin()` / ADMIN_EMAILS (`src/lib/auth/rbac.ts`) | fail-closed deny-all if unset; non-admin → 403. Systemic: SEC-0308 adds `middleware.ts` for `/api/admin/**` + ESLint `requireAdmin` rule. | | Ownership scoping | inline `where:{ id, userId }` / owner compare | cross-tenant read → 403/404 (needs 2 accounts to prove IDOR). | | Cron gate | `CRON_SECRET` (+ `?secret=` query compat, SEC-CRON-QUERY-COMPAT) | no/bad secret → 401 (fail-closed after SEC-0275/0276). | | Webhook sig | Stripe `constructEvent`, Telnyx/Slack/Telegram/WhatsApp sig, `verifyCloudWebhookSignature`, BlueBubbles `BRIDGE_SECRET` | bad sig → 401. | | Rate limit | `src/middleware/rateLimit.ts` `consumeRateLimit` | over-limit → 429. | | SSRF guard | `assertPublicHttpUrl` | loopback/link-local/redirect → blocked (SEC-0244/0292/0293/0294). | | Per-user LLM quota | `src/lib/ai/{budgets,serverApiKeys}` (SEC-0309 umbrella) | anon/over-quota on paid compute → 401/429, not silent server-key burn. | ## Verification rules (do not accept a fake-close) 1. **Valid-shape only.** A `400` on an empty body ≠ closed — the endpoint may still work on a well-formed unauth request. Only **401/403** (or 404-with-guard) on a **valid-shaped** unauth request proves closed. 2. `500` ≠ closed. `429`-only ≠ auth-closed (still reachable). A rate-limit 200 ≠ closed. 3. **AuthN ≠ AuthZ.** An IDOR (cross-tenant) needs **two accounts** — prove account-B cannot read account-A's resource, not merely that anon is blocked. 4. **Deployed ≠ enforcing.** Confirm the guard is wired (imported + on the request path), not just present as a lib. Prod = the SHA the Pi serves. 5. **Source ↔ prod drift.** This passport's 05 is generated from SOURCE (behind live). A route flagged "public/unauthed" in 05 may already be hardened on prod — check the SEC-* board status (06) and curl the live host, don't trust source alone. ## Live probe targets (prod `https://nb.wool2.online`) Curl valid-shaped unauth and assert the expected code. Board DoD map: `nc-agent-ops/board/dod-endpoint-map.json` (the `dod-verify-guard` runs these every 15min and auto-reopens false-closed → Needs Proof). | Endpoint | Method | Expect (closed) | Ticket | |---|---|---|---| | `/api/video/pipeline/run/` | GET/POST | 401/403 | SEC-0241/0242 | | `/api/video/pipeline/run//export` | GET | 401/403 | SEC-0243 | | `/api/video/pipeline/run//stream` | GET | 401/403 | SEC-0263 | | `/api/export/pdf/` (foreign) | GET | 403/404 | SEC-0264 | | `/api/admin/agents` | GET/POST | 401/403 | SEC-0295 | | `/api/admin/playwright` | POST | 401/403 | SEC-0296 | | `/api/admin/lighthouse` | GET/POST | 401/403 | SEC-0297 | | `/api/admin/set-tier` | POST | 403 (non-admin) | SEC-0260 | | `/api/admin/social/credentials` | GET/POST | 401/403 + no secret values | SEC-0245/0259 | | `/api/economy/earn` | POST | server-computed only, no client `source`/`amount` mint | SEC-ECONOMY-* | | `/api/media/[filename]` (traversal `../`) | GET | 400/403, no arbitrary read | SEC-0249 | | `/api/s/` (boundary) | GET | no prefix escape | SEC-0285 | | `/api/realtime` | GET/POST | 401 (no client_secret vend) | SEC-0299 | | `/api/deep-research/run` | POST | 401 | SEC-0305 | | `/api/slack/command` (unsigned) | POST | 401/503, not 200/500 | SEC-0300/0300B | | `/api/cron/process-messages` | GET | 401 (CRON_SECRET) | SEC-0252 | | `/api/db/health`, `/api/health` | GET | no raw DB errors / config leak | SEC-0286/0290/0291 | ## Still-open / watch (source-level, cross-check 06) - **iMessage/BlueBubbles webhook fail-open** if `BRIDGE_SECRET` unset (SEC-0310, Parked) — only live-open gap per 07-03 hardening review; Signal same latent. - Dev-WIP: SEC-0253/0254 (unauth transcription), SEC-LOGIN-THROTTLE-LOCKOUT. - Lead Review: SEC-ENV-BOOT-FAILCLOSED (process must not boot without required secrets). - Intake: SEC-ECONOMY-EPIC-SERVER-AUTHORITATIVE (whole credit-class umbrella). ## How to demand proof from the contour Ask for: (a) the **live curl** transcript (host + method + valid body + response code) not a source diff; (b) for IDOR, the **two-account** transcript; (c) the **serving SHA** (`/api/ops/live-sha` or systemd `ExecStart`) matching the claimed fix. Reject board-status-only ("Deployed") without a live valid-shape proof.