Skip to content
v1.9.5May 16, 2026

Massu v1.9.5

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-guar...

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 via execSync template-literal interpolation. Replaced with execFileSync argv form. Closes P-001 (Stage A). Drift-guard: packages/core/src/tests/hooks-no-shell-execsync.test.ts AST-scans every hook file and asserts zero execSync template-literal calls.
  • packages/core/src/commands/init.ts.mcp.json written 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 + new commands/hook-runner.ts — hook paths baked customer's npx-cache absolute paths into .claude/settings.json. Now emits npx -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>) orphaned MEMORY.md on 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-existent quality_metadata column. Now selects security_metadata. Closes P-005 (Stage A).
  • website/src/app/api/v1/audit/route.ts — filtered non-existent actor_id column. Now uses user_id. Closes P-006 (Stage A).
  • website/src/app/api/v1/team/route.ts — selected non-existent full_name. Now uses display_name. Closes P-007 (Stage A).
  • website/src/app/api/v1/cost/trend/route.ts — selected 4 non-existent columns. Now reads snapshot_date,total_cost_usd,total_tokens,cost_by_model,cost_by_feature with 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_statusstatus_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 generated Database type. 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 hardcoded created_at for ALL retention-managed tables, but session_transcripts uses column timestamp (010:38), aborting cleanup with column 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 not created_at no 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 every cutoff_col exists on its table in types.ts).
  • NEW migration website/supabase/migrations/026_service_role_detection_hardening.sql — migration 020 used a single current_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 original role GUC, (2) current_user = 'service_role' (covers SET ROLE service_role clients), (3) explicit app.is_service_role = 'true' GUC (operator-controlled SET LOCAL fallback). 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.ts asserts all three signals are present in the latest trigger definition.
  • packages/core/src/commands/init.ts mergeHooksConfig()installHooks() wholesale-replaced customer's hooks settings 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 + new website/src/components/redeem/fallback-logic.ts — direct /redeem traffic with no book_purchases row spun forever on 202 PURCHASE_PROCESSING. Added BONUS_FALLBACK_MS = 30_000 rescue: after 30s of polling, surfaces a /bonus rescue 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 atomic UPDATE 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 409 already_activated. (b) P-015 auth + email match — route was anonymous and resolved identity via user_profiles.email = purchase.email, allowing an attacker to sign up as victim@victim-corp.com and bind a leaked license key to the victim's org. Now requires createServerSupabaseClient().auth.getUser(); returns 401 with needs_signup hint (no email leak — anonymous enumeration vector closed) when unauthenticated; returns 403 when authedUser.email.toLowerCase() !== purchase.email.toLowerCase(). Both closed in Stage B. Drift-guard: integration/license-activate.test.ts covers race-loser, 401 unauth, 403 mismatch, AND case-insensitive matching.
  • NEW website/src/lib/sso-flag.ts + GATED website/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 via MASSU_SSO_ENABLED=false (default) until the library replacement (openid-client + @node-saml/node-saml) ships in plan-B.3-followup post-launch + third-party pen-test. Every SSO entry point returns canonical 503 SSO_DISABLED; dashboard SSO settings page shows maintenance UI with support@massu.ai contact 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 + call isSsoEnabled(), and that the callback route returns ssoDisabledResponse() from BOTH POST and GET handlers.
  • website/src/data/stats.ts — added MCP_TOOL_COUNT (= 73) named export alongside the existing stats: Stat[] SoT, plus WORKFLOW_COMMAND_COUNT, AI_AGENT_COUNT, LIFECYCLE_HOOK_COUNT. Replaced literal 84 MCP tools claims across 14 TSX/TS surfaces (app/layout.tsx 3x, app/docs/layout.tsx, app/features/page.tsx 4x, app/about/page.tsx, app/overview/page.tsx 5x, app/pricing/page.tsx, app/checkout/cancel/page.tsx, components/sections/Hero.tsx 2x, components/sections/OpenSourceSection.tsx, components/sections/CloudPreview.tsx, components/pricing/PricingFAQ.tsx, components/pricing/FeatureComparison.tsx, data/articles.ts 3x, data/features.ts, data/pricing.ts) with ${MCP_TOOL_COUNT} interpolation; replaced 84 with literal 73 in 2 MDX content files. Closes P-019 (Stage B). Drift-guard: NEW website/src/tests/marketing-tool-count-against-source-truth.test.ts walks website/src + website/content for forbidden literals (84 MCP, 84 tools, all 84, plus historical drift literals 47/56/62 MCP/tools) and asserts zero matches.
  • .github/workflows/ci.public.yml — both ci.yml and ci.public.yml had workflow name: CI and shared ${{ github.workflow }}-${{ github.ref }} concurrency group; on every main push the stronger ci.yml was being CANCELLED while the weaker ci.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. Renamed ci.public.yml workflow + concurrency group + job-display names to CI (public-mirror); 4 jobs gated if: github.repository != 'ethankowen-73/massu-internal' so the internal repo runs the FULL ci.yml and the public mirror keeps the public-facing subset. Job-level name: values PRESERVED (both rulesets reference them as required status checks). Closes P-020 (Stage A). Drift-guard: NEW website/src/tests/workflow-uniqueness.test.ts (3 assertions).
  • NEW website/src/app/book/opengraph-image.tsxbook/layout.tsx referenced /og-book.png but the file didn't exist in website/public/, producing a 404 image preview on every Twitter / LinkedIn / Slack share of /book. Replaced with the Next.js opengraph-image.tsx file convention — next/og ImageResponse generates 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.png references from book/layout.tsx. Closes P-021 (Stage B). Drift-guard: website/src/tests/og-book-image-resolves.test.ts (4 cases) — asserts the file exists, exports ImageResponse with 1200x630 dimensions, AND that any future reintroduction of /og-book.png in book/layout.tsx requires the static file to also exist.
  • website/src/components/sections/Hero.tsx — homepage had ZERO /book promotion above-the-fold; /book was 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.ts enforces the ordering invariant (book CTA must precede Install Free).
  • website/src/components/redeem/RedeemForm.tsxneeds_signup branch lost the license key, forcing customers to dig through email to re-paste the 28-char code after signup. Implemented 2-layer persistence: localStorage massu_pending_license_key (primary) + ?license_key= URL fallback. Auto-fills on mount; strips URL param after hydration (no Referer/history leak); persists on needs_signup; clears on success; signup CTA carries redirect_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 from website/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-tool mcp-bridge feature block — drops Feature Entries 167 → 163), website/src/app/overview/page.tsx changelog. 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 in packages/core/src/tools.ts — paying Pro customers would have received "Unknown tool" errors. Closes P-018 (Stage B).

Changed

  • website/src/data/stats.ts Feature Entries: 167 → 163 to match the P-018 MCP Bridge feature-block removal (4 tools × 1 entry each).
  • website/content/docs/reference/license-tiers.mdx Pro Tier header: 47 additional tools43 additional tools to match the P-018 MCP Bridge removal.
  • packages/core/package.json description: tier-count claim refreshed (12 free / 72 total)(12 free / 73 total), workflow-commands 55+59.
  • docs/plans/2026-05-16-prelaunch-audit-remediation.md Plan 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) is plan-B.3-followup post-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 /bonus not /redeem (defense-in-depth ships either way via P-013), (b) smoke-test current_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 --noEmit 0 errors.
  • packages/core/ TypeScript: npx tsc --noEmit 0 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.

Try this release

Install the latest version of Massu and start governing your AI development today.