Pre-launch comprehensive audit remediation — combined Stage A + Stage B release covering all 22 CRITICAL findings from the 14-agent adversarial audit fleet (docs/plans/2026-05-16-prelaunch-audit-remediation.md, plan token plan-2026-05-16-prelaunch-audit). Every fix is structural — paired with a vitest drift-guard that makes the bug class impossible to reintroduce (CR-46 compliant). 12 of 22 CRITICALs are security-critical (RCE, SSO bypass, license-takeover, schema drift causing 500s in production), addressed before book launch. The 1.9.4 hotfix slot was rolled into this combined release to ship Stage A + Stage B atomically. Also bundles the previously-shipped plan-rulesets-as-code Branch Protection rulesets-as-code migration (SHA b184d3f, drift-guarded by branch-protection-audit.yml).
Fixed
packages/core/src/hooks/fix-detector.ts:17,122,124— RCE viaexecSynctemplate-literal interpolation. Replaced withexecFileSyncargv form. Closes P-001 (Stage A). Drift-guard:packages/core/src/tests/hooks-no-shell-execsync.test.tsAST-scans every hook file and asserts zeroexecSynctemplate-literal calls.packages/core/src/commands/init.ts—.mcp.jsonwritten unpinned (npx -y @massu/core) resolved to stale global install. Now pinned to current installer version (getInstallerVersion()). Closes P-002 (Stage A). Drift-guard:tests/init-mcp-json-pin.test.ts.packages/core/src/commands/init.ts+ newcommands/hook-runner.ts— hook paths baked customer's npx-cache absolute paths into.claude/settings.json. Now emitsnpx -y @massu/core@<version> hook-runner <name>dispatch — version-pinned and machine-portable. Closes P-003 (Stage A). Drift-guard:tests/init-hook-paths-no-absolute.test.ts.- New
packages/core/src/lib/memory-path.ts— memory directory encoding inconsistency (--<root>vs-<root>) orphanedMEMORY.mdon 100% of fresh installs. Shared encoder/decoder with legacy-double-dash auto-migration. Closes P-004 (Stage A). Drift-guard:tests/memory-path-roundtrip.test.ts. website/src/app/api/v1/quality/route.ts— selected non-existentquality_metadatacolumn. Now selectssecurity_metadata. Closes P-005 (Stage A).website/src/app/api/v1/audit/route.ts— filtered non-existentactor_idcolumn. Now usesuser_id. Closes P-006 (Stage A).website/src/app/api/v1/team/route.ts— selected non-existentfull_name. Now usesdisplay_name. Closes P-007 (Stage A).website/src/app/api/v1/cost/trend/route.ts— selected 4 non-existent columns. Now readssnapshot_date,total_cost_usd,total_tokens,cost_by_model,cost_by_featurewith a post-query map preserving the external API response shape. Closes P-008 (Stage A).website/src/app/api/v1/webhooks/[id]/route.ts:34— surfaced bonus column-drift fix (response_status→status_code, cite: migration 014:31-37) caught on first run of the new drift-guard. Closes the CR-9 corollary to P-009 (Stage A).- NEW drift-guard
website/src/tests/api-v1-schema-parity.test.ts— depth-aware Tables parser + line-scanner pairs every.from().select()and.eq()/gte/lte/etc.filter call per route. Asserts every referenced column exists in the generatedDatabasetype. Closes the entire API-vs-schema bug class structurally (P-009 Stage A). - NEW migration
website/supabase/migrations/025_cleanup_org_records_column_fix.sql— migration 017 hardcodedcreated_atfor ALL retention-managed tables, butsession_transcriptsuses columntimestamp(010:38), aborting cleanup withcolumn created_at does not exist. Restructured the resource map to encode{table, cutoff_col}per resource_type — adding a future retention-managed table whose cutoff column is notcreated_atno longer requires CASE-branch sprawl. Migration 017's audit_log advisory-lock + trigger-disable pattern preserved. Closes P-010 (Stage B). Drift-guard:website/src/tests/cleanup-org-records-column-existence.test.ts(4 cases parsing the latest JSONB map and asserting everycutoff_colexists on itstableintypes.ts). - NEW migration
website/supabase/migrations/026_service_role_detection_hardening.sql— migration 020 used a singlecurrent_setting('role', true) = 'service_role'check whose behaviour against the production Supabase admin client was UNVERIFIED. Replaced with 3-signal defense-in-depth: (1) the originalroleGUC, (2)current_user = 'service_role'(coversSET ROLE service_roleclients), (3) explicitapp.is_service_role = 'true'GUC (operator-controlledSET LOCALfallback). Whichever pattern the admin client uses now or after a future client revision, detection succeeds — eliminates the "block-ALL-webhook-plan-upgrades" failure mode. Closes P-011 (Stage B). Drift-guard:website/src/tests/service-role-detection-defense-in-depth.test.tsasserts all three signals are present in the latest trigger definition. packages/core/src/commands/init.tsmergeHooksConfig()—installHooks()wholesale-replaced customer'shookssettings block (recurrence of 1.8.0 permissions-merge trap). Now deep-merges by event+matcher. Closes P-012 (Stage A). Drift-guard:tests/init-hooks-merge-preserves-customer.test.ts.website/src/components/redeem/RedeemForm.tsx+ newwebsite/src/components/redeem/fallback-logic.ts— direct/redeemtraffic with nobook_purchasesrow spun forever on 202 PURCHASE_PROCESSING. AddedBONUS_FALLBACK_MS = 30_000rescue: after 30s of polling, surfaces a/bonusrescue link. Closes P-013 UI defense-in-depth half (Stage A). Drift-guard:tests/redeem-fallback-logic.test.ts(9 cases).website/src/app/api/license/activate/route.ts— TWO CRITICAL hardenings in one route refactor: (a) P-014 atomic UPDATE — SELECT-check-UPDATE pattern allowed two concurrent requests with the same key to both pass the JS-side status check and double-activate. Replaced with atomicUPDATE book_purchases SET license_key_status='active', activated_at=NOW() WHERE id=$1 AND license_key_status='inactive' .select('id, activated_at'). Race losers see[]from the post-update select and surface 409already_activated. (b) P-015 auth + email match — route was anonymous and resolved identity viauser_profiles.email = purchase.email, allowing an attacker to sign up asvictim@victim-corp.comand bind a leaked license key to the victim's org. Now requirescreateServerSupabaseClient().auth.getUser(); returns 401 withneeds_signuphint (no email leak — anonymous enumeration vector closed) when unauthenticated; returns 403 whenauthedUser.email.toLowerCase() !== purchase.email.toLowerCase(). Both closed in Stage B. Drift-guard:integration/license-activate.test.tscovers race-loser, 401 unauth, 403 mismatch, AND case-insensitive matching.- NEW
website/src/lib/sso-flag.ts+ GATEDwebsite/src/app/api/sso/callback/route.ts,website/src/app/api/sso/route.ts,website/src/app/dashboard/settings/sso/page.tsx— the custom OIDC verifier never cryptographically verified the JWT signature against the IdP's JWKS (P-016 — full SSO bypass for any attacker who registers their own OIDC config), and the custom SAML verifier was vulnerable to XML Signature Wrapping (P-017 — the first-element-wins regex parser allowed outer-wrapper substitution while inner signature still validated). Per operator decision (2026-05-16), SSO is DISABLED viaMASSU_SSO_ENABLED=false(default) until the library replacement (openid-client+@node-saml/node-saml) ships inplan-B.3-followuppost-launch + third-party pen-test. Every SSO entry point returns canonical 503SSO_DISABLED; dashboard SSO settings page shows maintenance UI withsupport@massu.aicontact path. Legacy verifier code preserved below the gate so the followup PR can wire libraries without re-deriving the Supabase user-creation flow. Both closed at the entry point in Stage B. Drift-guard:website/src/tests/sso-disabled-by-default.test.ts(10 cases) asserts the flag defaults false, that all three entry-point files import + callisSsoEnabled(), and that the callback route returnsssoDisabledResponse()from BOTH POST and GET handlers. website/src/data/stats.ts— addedMCP_TOOL_COUNT(= 73) named export alongside the existingstats: Stat[]SoT, plusWORKFLOW_COMMAND_COUNT,AI_AGENT_COUNT,LIFECYCLE_HOOK_COUNT. Replaced literal84 MCP toolsclaims across 14 TSX/TS surfaces (app/layout.tsx3x,app/docs/layout.tsx,app/features/page.tsx4x,app/about/page.tsx,app/overview/page.tsx5x,app/pricing/page.tsx,app/checkout/cancel/page.tsx,components/sections/Hero.tsx2x,components/sections/OpenSourceSection.tsx,components/sections/CloudPreview.tsx,components/pricing/PricingFAQ.tsx,components/pricing/FeatureComparison.tsx,data/articles.ts3x,data/features.ts,data/pricing.ts) with${MCP_TOOL_COUNT}interpolation; replaced84with literal73in 2 MDX content files. Closes P-019 (Stage B). Drift-guard: NEWwebsite/src/tests/marketing-tool-count-against-source-truth.test.tswalkswebsite/src+website/contentfor forbidden literals (84 MCP,84 tools,all 84, plus historical drift literals47/56/62 MCP/tools) and asserts zero matches..github/workflows/ci.public.yml— bothci.ymlandci.public.ymlhad workflowname: CIand shared${{ github.workflow }}-${{ github.ref }}concurrency group; on every main push the strongerci.ymlwas being CANCELLED while the weakerci.public.yml(4 of 6 internal jobs only — missing Pattern Scanner, Security Scanner, Generalization Scanner, Plan Status Validator, Plan Commit Drift, Plan-Token Changelog Coverage, Website Audit, Hook Build) satisfied branch protection. This is how the 4 API column-drift CRITICALs (P-005..P-008) reached production. Renamedci.public.ymlworkflow + concurrency group + job-display names toCI (public-mirror); 4 jobs gatedif: github.repository != 'ethankowen-73/massu-internal'so the internal repo runs the FULLci.ymland the public mirror keeps the public-facing subset. Job-levelname:values PRESERVED (both rulesets reference them as required status checks). Closes P-020 (Stage A). Drift-guard: NEWwebsite/src/tests/workflow-uniqueness.test.ts(3 assertions).- NEW
website/src/app/book/opengraph-image.tsx—book/layout.tsxreferenced/og-book.pngbut the file didn't exist inwebsite/public/, producing a 404 image preview on every Twitter / LinkedIn / Slack share of/book. Replaced with the Next.jsopengraph-image.tsxfile convention —next/ogImageResponsegenerates a brand-consistent 1200x630 PNG at build time, Next.js auto-wires<meta property="og:image">and<meta name="twitter:image">. No static asset to maintain. Removed the broken/og-book.pngreferences frombook/layout.tsx. Closes P-021 (Stage B). Drift-guard:website/src/tests/og-book-image-resolves.test.ts(4 cases) — asserts the file exists, exportsImageResponsewith 1200x630 dimensions, AND that any future reintroduction of/og-book.pnginbook/layout.tsxrequires the static file to also exist. website/src/components/sections/Hero.tsx— homepage had ZERO/bookpromotion above-the-fold;/bookwas only reachable via 7th nav tab. Added a pill-style<Link href="/book">book-announcement BEFORE the existing "Free & Source Available" badge AND before the Install Free button.trackEvent('cta_hero_book_announcement')for funnel measurement. Closes P-022 (Stage B). Drift-guard:website/src/tests/homepage-book-cta-presence.test.tsenforces the ordering invariant (book CTA must precede Install Free).website/src/components/redeem/RedeemForm.tsx—needs_signupbranch lost the license key, forcing customers to dig through email to re-paste the 28-char code after signup. Implemented 2-layer persistence: localStoragemassu_pending_license_key(primary) +?license_key=URL fallback. Auto-fills on mount; strips URL param after hydration (no Referer/history leak); persists onneeds_signup; clears on success; signup CTA carriesredirect_to=/redeem?license_key=…. Closes P-023 (Stage B). Drift-guard:tests/redeem-license-key-persistence.test.ts(7 cases).
Removed
- DELETED
website/content/docs/features/mcp-bridge.mdx+ purged MCP Bridge references fromwebsite/src/data/docs-nav.ts,website/content/docs/reference/tool-reference.mdx(the "MCP Bridge Tools (4)" section),website/content/docs/reference/license-tiers.mdx(Pro tier 47 → 43 tools),website/src/components/pricing/PricingFAQ.tsx(2 FAQ entries removed),website/src/data/features.ts(the entire 4-toolmcp-bridgefeature block — drops Feature Entries 167 → 163),website/src/app/overview/page.tsxchangelog. Operator decision: REMOVE not implement. Vapor MCP Bridge tools (mcp_servers,mcp_tools,mcp_call,mcp_status) were documented at full feature page level but did NOT exist inpackages/core/src/tools.ts— paying Pro customers would have received "Unknown tool" errors. Closes P-018 (Stage B).
Changed
website/src/data/stats.tsFeature Entries: 167 → 163 to match the P-018 MCP Bridge feature-block removal (4 tools × 1 entry each).website/content/docs/reference/license-tiers.mdxPro Tier header:47 additional tools→43 additional toolsto match the P-018 MCP Bridge removal.packages/core/package.jsondescription: tier-count claim refreshed(12 free / 72 total)→(12 free / 73 total), workflow-commands55+→59.docs/plans/2026-05-16-prelaunch-audit-remediation.mdPlan Status header: "IN PROGRESS — Stage A + Stage B CODE COMPLETE (awaiting operator release ceremony); Stages C-E DRAFT" → SHIPPED. Plan documents Stages C-E (38 HIGH + 52 MEDIUM + 71 LOW + INFO) as draft work that will ship post-launch in the 1.10.x / 1.11.x / rolling cluster cadence.
Known follow-on
- P-016 / P-017 library replacement is NOT in this release. The security risk is closed at the entry point (every SSO request returns 503; legacy verifier code unreachable). The full library replacement (
openid-client+@node-saml/node-saml) isplan-B.3-followuppost-launch after a third-party pen-test, per the operator decision recorded in the master plan. - Operator manual verification still required (these cannot be automated): (a) printed Amazon book URL points to
/bonusnot/redeem(defense-in-depth ships either way via P-013), (b) smoke-testcurrent_setting('role')against the production admin client to confirm the 3-signal trigger admits the actual webhook caller (P-011), (c) each Lemon Squeezy variant has the ebook file attached (P-H015).
Verification
packages/core/: 156 test files / 2235 tests PASS (12 skipped) on Node 22.website/: 46 test files / 439 tests PASS (includes 46 NEW Stage B drift-guard cases across 8 new/updated test files + 8 Stage A drift-guards already shipped).website/TypeScript:npx tsc --noEmit0 errors.packages/core/TypeScript:npx tsc --noEmit0 errors.- Node 26 local builds blocked by pre-existing better-sqlite3 native-binding issue (memory
feedback_better_sqlite3_native_binding_missing.md); CI runs on Node 22 cleanly.