Stage C Release 2 — pre-launch audit HIGH-severity sub-stages C.3 (schema/DB, 4 items) + C.5 (security defense-in-depth, 5 of 7 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 9 items shipped this release. Two C.5 items (P-H019 Ed25519 license signing + P-H022 nonce-based CSP migration) are deferred to dedicated follow-up sub-plans because each requires non-trivial server-side counterparts (P-H019: AWS Secrets Manager key + signing route; P-H022: per-page inline-script audit) that must be done with operator coordination and proper testing scope — NOT release valves per CR-46, but legitimate scope-splitting where the structural foundation needs the operator's environment access.
Added
website/supabase/migrations/027_drop_contact_submissions_anon_insert.sql— drops theallow_anon_insertRLS policy oncontact_submissionsthat previously let the public anon key bypass/api/contact's rate-limit + Zod sanitization. The API route uses the service-role client (RLS-exempt), so the anon policy was unnecessary AND a security footgun. P-H021.website/src/lib/ip/get-client-ip.ts— canonical client-IP extractor preferringx-real-ip(trusted on Vercel) over RIGHTMOSTx-forwarded-forhop. P-H018 — replaces 19 callsites that trusted the LEFTMOST attacker-controlled XFF hop.website/src/tests/get-client-ip-precedence.test.ts— 7-case drift-guard asserting precedence + AST-scan ban on any directheaders.get('x-forwarded-for')outside the helper.website/src/tests/webhook-url-allowlist-completeness.test.ts— 11-case drift-guard for the new validateWebhookUrl gaps (P-H024).packages/core/src/tests/memory-db-cascade-delete-session.test.ts— drift-guard for P-H011 cascade behavior (source scan + live cascade verification on a fresh DB).
Fixed
website/src/lib/supabase/types.ts— addedtrial_email_logtype definition (Row/Insert/Update + Relationships) per migration 024. Removed thesupabase as unknown as ...cast hack atapp/api/license/activate/route.ts:421-425. Comment about "migration 023" stale-referenced removed. P-H010.packages/core/src/memory-db.ts— 10 FOREIGN KEY references tosessions(session_id)now declareON DELETE CASCADE(was: implicit RESTRICT). Closes the "DELETE FROM sessions with surviving children throws" class underPRAGMA foreign_keys = ON. Note: existing customer DBs from prior versions retain non-cascade tables (CREATE TABLE IF NOT EXISTS no-ops); fix takes effect for fresh installs from 1.10.2 onward. P-H011.packages/core/src/memory-db.ts:630-657—dequeuePendingSyncno longer silently discards queue items atretry_count >= 10. Now emits a stderr warning with recent error messages AND inserts acloud_sync_giveuprow intoanalytics_eventsso the customer can detect silent cloud-sync failure (e.g., invalid API key for >10 cycles losing all queued observations). P-H012.packages/core/src/knowledge-db.ts:107-119—knowledge_schema_mismatches.sourcecolumn no longer has a SQL DEFAULT that was a JS template-literal interpolation baked into the customer's SQLite at schema creation time (so later config changes were ignored). Default is now applied at INSERT time viagetConfig().conventions.knowledgeSourceFiles[0]inknowledge-indexer.ts:443-447. P-H013.- 19 route handlers (
api/settings/route.ts,api/contact/route.ts,api/evidence/route.ts,api/license/activate/route.ts,api/github-stars/route.ts,api/sso/route.ts,api/sso/callback/route.ts,api/keys/route.ts,api/keys/[id]/route.ts,api/export/route.ts,api/lemon-squeezy/webhook/route.ts,api/stripe/checkout/route.ts,api/badge/[orgSlug]/[type]/route.ts,api/invitations/accept/route.ts) — replacedrequest.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'withgetClientIp(request). Closes the attacker-controllable IP bypass class. P-H018. packages/core/src/cloud-sync.ts:98-128— payload filter now consumesclassifyVisibility()fromobservation-extractor.tsto drop observations whose title/detail/file_path matchPRIVATE_PATTERNS(Stripe keys, env var names, file paths, Bearer tokens, etc.). Pre-fix: cloud-sync transmitted every observation to Massu's Supabase, leaking customer secrets. Now drops privately-classified observations with stderr count for transparency. P-H020.website/src/app/api/license/activate/route.ts:17-39—hashIp()no longer falls back toLEMON_SQUEEZY_WEBHOOK_SECRETand no longer silently uses empty pepper.IP_HASH_PEPPERis now REQUIRED; absence throws with actionable error. Closes the cross-purpose-key-reuse vulnerability where a leakedlicense_activation_attemptstable would let attackers recover the webhook-signing secret via rainbow-table inversion. P-H023. Operator action required: setIP_HASH_PEPPERenv var to a distinct random value (openssl rand -hex 32) — NOT reused from any other secret.website/src/lib/validations.ts:199-260—validateWebhookUrlSSRF allowlist extended: rejects0.0.0.0/8(was: only exact 0.0.0.0), CGNAT100.64.0.0/10, IPv6 ULAfc00::/7, IPv6 link-localfe80::/10, IPv4-mapped IPv6 loopback::ffff:127.x.x.x, and IPv6::1(canonical form). P-H024. DNS-rebinding pin documented as follow-up (requires fetch-dispatcher rework).website/src/tests/integration/license-activate.test.ts:99-110— addedvi.stubEnv('IP_HASH_PEPPER', ...)to beforeEach since P-H023 made the env var REQUIRED. Test deterministically uses a fixed pepper string.
Deferred to Follow-up Sub-Plans
- P-H019 Ed25519 license signing →
plan-license-response-signing-server-side(TBD). Requires: (a) AWS Secrets Manager key creation by operator, (b) server-side signing route inwebsite/src/app/api/license/validate/route.ts, (c) client-side verifier inpackages/core/src/security/, (d) 24h grace period for existing unsigned-cache acceptance, (e) cutover smoke test against production. Current vulnerable behavior preserved until follow-up ships. - P-H022 nonce-based CSP migration →
plan-csp-nonce-migration(TBD). Requires: (a) per-page audit of every inline<script>and<style>inwebsite/src/app/, (b) middleware nonce generation + injection into request headers, (c) Next.js consumption pattern (read x-nonce from headers() in layout), (d) tightening CSP one-source-at-a-time with smoke testing each page, (e) drift-guard test. Current'unsafe-inline'CSP preserved until follow-up ships.