Gate 1 — canonical state audit: this workstation's HEAD/origin/main and every claimed milestone SHA (Phase 2A through 9.1) verified consistent; no stranded branches/tags on Gitea; full-history secret scan clean (no rotation required); Phase 9.5 confirmed absent from canonical history (pricing was given directly by Chase and used without it); second-PC reconciliation commands provided since this session cannot see that machine. Gate 3 — secrets: real inventory derived from process.env greps across every service (not guessed); Infisical auth fixed (wrong domain was the actual root cause of Phase 9.1's failures — the identity belongs to infisical.cedshomelab.com, not app.infisical.com) but no RootNode Academy project exists there yet, contradicting this phase's own authorization — flagged for Chase to create. Gates 4-9/15-16 — production: verified, not assumed, that no dedicated server/Proxmox VM has been provisioned (empty terraform.tfvars, no state file, placeholder group_vars/production.yml) — IaC validated (tofu validate, ansible --syntax-check both clean) and left unexecuted per this phase's own 'do not fake production deployment' instruction. rootnodeacademy.com confirmed live (Cloudflare, real HTTPS) — the separate, not-RootNode-owned website; no Platform API subdomain exists yet. Gate 18 — security audit: full review against every regression suite rerun this phase; no launch-critical defect found. Gates 19/20 — full regression (131 checks: e2e-phase5b/5c/6f/7/8/9, content validation, migrations, frontend build) and the Winter Break commerce rehearsal, both rerun clean against current main. Gates 10-14/21 — website integration, Winter Break pricing (99/49, real, Stripe-validated), agreements/legal, tax, and refund policy all reviewed; legal text/tax/refund policy/dates/venue/ capacity correctly left as open owner decisions, not invented. No application code changed this phase — audit/launch-prep only.
45 lines
5.2 KiB
Markdown
45 lines
5.2 KiB
Markdown
# Phase 10 — Security Audit
|
|
|
|
Gate 18. A production-oriented review of RootNode Academy's security posture, drawing on every
|
|
regression suite rerun this phase (`docs/PHASE10_LAUNCH_READINESS.md`) plus direct code review.
|
|
No genuine launch-critical defect was found this phase — the one real defect Phase 10's
|
|
predecessor phases exposed (Phase 9.1's webhook-correlation bug) was already fixed and
|
|
regression-tested before this phase began.
|
|
|
|
| Area | Status | Evidence |
|
|
|---|---|---|
|
|
| Student authentication | ✅ Verified | `e2e-phase5b.js`/`e2e-phase5c.js` rerun clean this phase — activation, single-use tokens, expiration, password reset with anti-enumeration, session revocation on reset. |
|
|
| Instructor authentication | ✅ Verified | Same suites — instructor login, RBAC roles (owner/admin/instructor). |
|
|
| Sessions | ✅ Verified | Persistent, server-controlled sessions (Phase 3A); a real Platform API restart preserves sessions/enrollment state (rerun clean this phase). |
|
|
| Password reset / activation | ✅ Verified | Single-use, server-side expiration, no plaintext token ever returned by any admin API (`toAdminView` never includes it). |
|
|
| RBAC | ✅ Verified | Admin/owner-only routes (guardian detail, payment data) vs. any-instructor routes (offering/location lists) — reconfirmed across Phase 7/8/9 suites this phase. |
|
|
| Cohort scoping | ✅ Verified | An instructor sharing a cohort can manage; an unrelated instructor cannot (Phase 5B/7 suites). |
|
|
| CSRF | ✅ Present, unchanged | `platform/api/src/security.js` + per-route session/cookie checks (student/instructor auth routes) — not modified this phase, no regression. |
|
|
| Rate limiting / lockout | ✅ Present, unchanged | Login-attempt tracking in `security.js` (matches the existing sweeper pattern also used for session expiry). |
|
|
| Audit logging | ✅ Verified | Every admin/integration action recorded; audit-hygiene test (no secrets/PII in audit metadata) rerun clean in `e2e-phase9.js`. |
|
|
| Server-authoritative pricing | ✅ Verified, real Stripe | Phase 9.1 proved a tampered caller amount is ignored against **Stripe's own recorded object**, not just RootNode's database. |
|
|
| Agreement enforcement | ✅ Verified | Checkout blocked (`409`) until acceptance, in both the deterministic suite and real Stripe TEST mode. |
|
|
| Capacity enforcement | ✅ Verified | Race-safe seat reservation (`reserved_seats < capacity` in one UPDATE) — last-seat race test rerun clean this phase. |
|
|
| No card-data storage | ✅ Verified | `payment_transactions` schema has no card fields; explicit admin-view test asserts none appear; Stripe's hosted Checkout is the only thing that ever sees a real card. |
|
|
| Webhook signature verification | ✅ Verified, real Stripe | Invalid/forged signatures rejected (both deterministic and real-Stripe-signed tests); idempotent via a real DB primary key on the provider's event id, reconfirmed with a genuinely re-signed real Stripe event in Phase 9.1. |
|
|
| Logging/audit redaction | ✅ Verified | This session's own Stripe secret/webhook-secret values were grepped across every log/scratch file produced — none found; `e2e-phase9.js`'s hygiene test asserts no `sk_`/`whsec_`/card-number pattern in audit metadata or metrics text. |
|
|
| Secrets never committed | ✅ Verified | Full-history pickaxe search this phase — clean (`docs/PHASE10_CANONICAL_STATE_AUDIT.md` §1.6). |
|
|
| mTLS (Provisioner ↔ Docker, Gateway ↔ Lab Host Router) | ✅ Present, unchanged, not re-verified live | Ansible `docker-tls` role and TLS env vars exist; cannot be exercised against a real mTLS handshake without real hardware (Gate 4/5's hardware blocker). |
|
|
| Privileged lab boundary / RFC1918 egress denial | ✅ Present, unchanged | `provisioner/src/egress.js`; this phase reconfirmed the module exists and is wired, did not re-run Phase 1/2's own dedicated isolation drills (out of this phase's changed-code scope — nothing touched networking code). |
|
|
| Production cookie settings (`COOKIE_SECURE`) | ✅ Present, verified pattern | `e2e-phase5c.js`'s own test explicitly proves `COOKIE_SECURE=1` production gating via a real container recreate — reconfirmed present, not re-executed as a fresh drill this phase since nothing in that code path changed. |
|
|
| HTTPS end-to-end | ⏸ Cannot verify — no production host | `COOKIE_SECURE=1`/`TRUST_PROXY=1` require a real HTTPS-terminating reverse proxy in front of a real Gateway, which doesn't exist yet (Gate 4-6's hardware blocker). The marketing site at `rootnodeacademy.com` IS real HTTPS (Cloudflare) — the Platform API itself has no production endpoint yet. |
|
|
|
|
## Findings requiring action
|
|
|
|
**None launch-critical within existing architecture.** Everything marked ⏸ above is blocked on
|
|
the same missing production server documented in `docs/PHASE10_PRODUCTION_DEPLOYMENT.md` — not
|
|
a code defect, and building around it (e.g. faking an HTTPS check) would violate this phase's
|
|
own "do not fake production readiness" instruction.
|
|
|
|
## Scope discipline
|
|
|
|
This audit reviewed and reconfirmed existing security properties; it did not expand into new
|
|
security tooling, new scanning infrastructure, or unrelated hardening work not already part of
|
|
Phase 1-9's architecture, per Phase 10's own "do not expand into unrelated feature work"
|
|
instruction.
|