Skip to content
v1.11.0May 17, 2026

**Stage D — MEDIUM-severity sweep (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-d-medium-sweep`)**.

**Stage D — MEDIUM-severity sweep (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-d-medium-sweep`)**. First of the two Stage D patch releases. Bundles D.1 (DB lifecycle, 10 items), D.2 (API webhook hygiene, 14 items), D.3 (revenue + cron, 6 items), D.4 (architecture cleanup, 7 items), and the SQ...

Stage D — MEDIUM-severity sweep (parent plan plan-2026-05-16-prelaunch-audit, sub-plan plan-stage-d-medium-sweep). First of the two Stage D patch releases. Bundles D.1 (DB lifecycle, 10 items), D.2 (API webhook hygiene, 14 items), D.3 (revenue + cron, 6 items), D.4 (architecture cleanup, 7 items), and the SQL-LIMIT structural drift-guard (P-DG-001). 37 medium-severity items + 1 structural rule = 38 deliverables shipped under Stage D's first release. The second release 1.11.1 will bundle D.5–D.6 and two more drift-guards.

This release intentionally pauses before tag / publish / sync / deploy per the operator's 2026-05-17 scope decision — all code changes are committed and gates pass, but the ceremony is deferred to a separate session for operator approval.

Added

  • website/src/lib/audit-write.ts + website/supabase/migrations/040_audit_log_unattributed.sql (P-M-034) — single helper for every audit_log write (28+ callsites migrated). Null-org events route to a new audit_log_unattributed table instead of being silently skipped. Pattern Scanner Check 22 + drift-guard audit-write-coverage.test.ts enforce the structural ban on direct from('audit_log').insert(...) outside the helper.
  • website/src/data/lemon-squeezy-config.ts (P-M-025) — single SoT for Lemon Squeezy variant→tier mapping + test/live mode flag. Drift-guard lemon-squeezy-variants-sot.test.ts forbids hardcoded variant URLs outside the SoT.
  • website/src/app/api/auth/rate-limit-probe/route.ts (P-M-017) — server-side /login rate-limit probe (5/min per email + 20/min per IP). Client cannot bypass with multi-tab. Drift-guard login-server-rate-limit.test.ts (5 cases).
  • website/src/app/api/ebook/download/route.ts (P-M-027) — entitlement-gated ebook download path with 5-minute signed URLs as a defense-in-depth fallback alongside Lemon Squeezy's primary fulfillment. Requires operator to upload PDF+EPUB to ebook-fulfillment-fallback bucket before deploy.
  • website/src/lib/crypto/constant-time-compare.ts (P-M-019) — crypto.timingSafeEqual-backed helper to replace !== on auth headers (CRON_SECRET path migrated; drift-guard test enforces).
  • Pattern Scanner Check 19 — bans console.log/error/warn on hot paths in packages/core/src (allowlist via inline // @stdout-allow: marker). Closes wave2-architecture F-ARCH-008 (P-M-035).
  • Pattern Scanner Check 21 — caps packages/core/src TypeScript modules at 1000 LOC (allowlist via // @scanner-allow:large-file marker). Closes wave2-architecture F-ARCH-004 (P-M-031). The two known >1000 LOC files (knowledge-tools.ts, memory-db.ts, plus tools.ts + commands/init.ts caught by the check at ship time) carry explicit allowlist markers documenting the deferred mechanical decomposition.
  • Pattern Scanner Check 22 — bans direct from('audit_log').insert(...) outside audit-write.ts (P-M-034).
  • Pattern Scanner Check 23 — warns on new // TODO|FIXME|workaround|for now|good enough comments missing a @plan:<token> or @issue:<#> allowlist marker (P-M-037).
  • Pattern Scanner Check 25 + ESLint rule massu/no-unbounded-sql-all (P-DG-001) — forbid db.prepare(SELECT ...).all() chains without a LIMIT clause. The ESLint rule is the authoritative AST gate (wired into website/eslint.config.mjs); Check 25 is the grep-level safety net for environments where ESLint isn't invoked. Drift-guard eslint-rule-no-unbounded-sql-all.test.ts (7 cases) exercises the rule via hand-built ESTree fragments.
  • governance_rules: top-level field in massu.config.yaml schema (P-M-036) — customer-authored CR-style rules loaded into knowledge_rules with source='customer-config'. Distinct from the existing rules: path-scoped lint-hints field. Drift-guard custom-governance-rules-config-loading.test.ts (4 cases) pins the cross-contamination invariant. New docs page website/content/docs/reference/custom-governance-rules.mdx explains the two fields' separate purposes.
  • 13 new Supabase migrations (028040) plus their drift-guards (migration-partial-apply-safety, migration-idempotency, migration-service-role-policy-coverage, handle-new-user-email-confirm-gate, migration-grant-discipline). Closes the partial-apply window + ON CONFLICT idempotency + explicit service_role policy + blanket-grant discipline bug classes from wave1-schema-sync.

Changed

  • D.1 hookspre-compact.ts LIMIT 1000 (P-M-001); post-tool-use.ts module-scope mtime-cached readConventions() (P-M-002); fix-detector.ts skip-on-slow-git auto-disable (P-M-003); 10 hooks standardized on JSON.stringify({message}) output via new hooks/lib/write-hook-message.ts helper (P-M-004).
  • D.1 architecturememory.db + knowledge.db now opened once per dispatcher process via server-dispatch.ts cache (P-M-010), matching the codegraph.db + data.db pattern from Stage C plan-1.6.2.
  • D.2 webhook dispatcherdeliverWebhook() re-validates the URL pulled from the DB before fetch AND pins the resolved IP via undici Agent connect.lookup to defeat DNS rebinding (P-M-012). New validateResolvedAddress() mirrors the IP-blocklist checks for resolved addresses. Drift-guards webhook-dispatcher-revalidates.test.ts + dns-rebinding-resistant.test.ts.
  • D.2 SSO OIDC callback — config_id encoded into OAuth state (base64url JSON {v:1, config_id, nonce}); callback routes directly to the named config (P-M-016). Closes the try-each enumeration timing leak and premature-code-consumption risk. Drift-guard oidc-callback-state-routing.test.ts (5 cases).
  • D.2 stripe webhook — handler now calls a single stripe_event_apply RPC (migration 036) that wraps idempotency + plan update + audit_log + activity_feed in one Postgres transaction (P-M-018/024). Closes the SELECT-then-INSERT race window AND the audit-mid-failure-leaves-plan-updated partial-write class.
  • D.3 book purchases — RLS hardening: SECURITY DEFINER book_purchases_safe view + role-gated SELECT on the underlying table (owners + admins only see PII / license_key columns). Migration 034. Drift-guard book-purchases-role-gated.test.ts.
  • D.3 cron trial-email idempotencycron_acquire_email_lock + cron_record_email_failure RPCs (migration 037) bundle email-send + log-insert into a single transaction with 72h retry semantics. Closes the silent-skip-on-log-INSERT-failure window from wave2-book-redeem F8 (P-M-026).
  • D.4 license cache (P-M-023) — Ed25519 signed payload column on license_cache (in-place schema upgrade via PRAGMA introspection). On cache read, the validator verifies the stored signature and re-extracts trusted fields from the verified payload — direct SQLite edits to tier / valid_until columns are structurally a no-op. Strict mode (MASSU_REQUIRE_SIGNED_LICENSE=true) rejects unsigned rows entirely; transition mode (default) emits a one-shot stderr warning.
  • D.4 tier metadata bijection — runtime assertion at annotateToolDefinitions() end pins TOOL_TIER_MAP as the single source for annotations.tier + description prefix (P-M-033). Drift-guard tier-metadata-bijection.test.ts (5 cases).
  • D.4 governance_rules schemagovernance_rules: top-level field added to massu.config.yaml Zod schema (P-M-036). Loaded into knowledge_rules at config-refresh time with source='customer-config' (new column on the table, added via PRAGMA-introspected ALTER).

Fixed

  • D.2 API hygiene/api/v1/audit?actor= returns clear note vs. silent empty for actor-no-rows vs org-no-rows (P-M-011); /api/v1/audit/report LIMIT 10000 + cursor pagination (P-M-013); evidence PDF downloads now write to audit_log + are per-IP rate-limited 30/60s (P-M-014); SAML Audience element is MANDATORY (was if (audience && ...)) (P-M-015); lib/rate-limit.ts fail-closed in production (P-M-022).
  • D.3 redemption surface/activate page DELETED with permanent 301 redirect to /redeem, removed from sitemap, internal links audited (P-M-028).
  • D.3 billing anchororganizations.billing_period_start column added (migration 039) and populated by Stripe checkout + Lemon Squeezy activate handlers + backfilled via trial_ends_at - INTERVAL '<n> days' (P-M-029). prevent_billing_column_tampering trigger extended.
  • D.4 hot-path stdoutvalidate-features-runner.ts 8 sites + tree-sitter-loader.ts 2 sites migrated from console.* to process.stderr.write with explicit \n terminators (P-M-035). Pattern Scanner Check 19 prevents recurrence.

Removed

  • /activate page (P-M-028) — duplicate redemption surface with worse retry UX than /redeem. Permanent 301 redirect via next.config.mjs ensures bookmarks + email links keep working.
  • @massu/adapter-phoenix, @massu/adapter-aspnet, @massu/adapter-go-chi workspace packages plus their detect/adapters/ re-export shims and test files (P-M-032). Each had exactly ONE consumer (a 1-line re-export). Drift-guard adapter-package-consumer-tracking.test.ts detects any future zero-consumer adapter package. Operator action required AFTER publish: npm deprecate '@massu/adapter-phoenix@' '@massu/adapter-aspnet@' '@massu/adapter-go-chi@'. Rails + Spring retained per operator decision (Stage E reassessment with adoption telemetry).
  • tools.ts:103,207,261 direct config.framework.router/.orm === comparisons were already migrated to supportsRouter() / supportsOrm() helpers in 1.10.8 — this release adds no further removals in that lane.

Security

  • D.2 webhook SSRF defense-in-depth (P-M-012) — DNS rebinding defeated via undici Agent connect.lookup IP-pinning. Re-validation at delivery time means a URL that passed create-time validation but flipped its DNS post-validation is still blocked.
  • D.2 cron auth constant-time (P-M-019) — crypto.timingSafeEqual replaces !== on CRON_SECRET comparison. Closes the theoretical character-by-character timing oracle.
  • D.2 book_purchases RLS (P-M-020) — license_key + PII columns no longer SELECTable by non-admin org members. Member-tier dashboards read from the book_purchases_safe view.
  • D.2 grant discipline (P-M-021) — blanket GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated from migration 001 replaced by per-table explicit grants. New tables require explicit grants or a -- @no-grant: comment.
  • D.2 rate-limit fail-closed in production (P-M-022) — lib/rate-limit.ts throws RateLimitFailClosedError at module-load time when VERCEL_ENV === 'production' AND Upstash env vars are absent. Closes the per-Vercel-region cold-start enforcement gap.
  • D.4 license cache signing (P-M-023) — see above. Editing license_cache directly in SQLite no longer grants any tier.

Try this release

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