Skip to content
v1.10.5May 17, 2026

**P-H019 Ed25519 license signing** — closes the deferred follow-up from Stage C (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-c-high-batch`).

**P-H019 Ed25519 license signing** — closes the deferred follow-up from Stage C (parent plan `plan-2026-05-16-prelaunch-audit`, sub-plan `plan-stage-c-high-batch`). Pre-fix `packages/core/src/license.ts:280-318` accepted ANY `{valid:true,plan:'enterprise'}` JSON over HTTPS from whatever `config.cloud?.endpoint` poin...

P-H019 Ed25519 license signing — closes the deferred follow-up from Stage C (parent plan plan-2026-05-16-prelaunch-audit, sub-plan plan-stage-c-high-batch). Pre-fix packages/core/src/license.ts:280-318 accepted ANY {valid:true,plan:'enterprise'} JSON over HTTPS from whatever config.cloud?.endpoint pointed to. MITM, malicious cloud.endpoint, or local SQLite edit of license_cache could grant arbitrary tier.

Added

  • packages/core/security/license-pubkey.pem — Ed25519 public key (fingerprint 18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475) for verifying signed validate-key responses. Counterpart private key is operator-provisioned in the Supabase Edge Function env var LICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64 (NEVER in repo).
  • scripts/bundle-license-pubkey.mjs — bundler that writes packages/core/src/security/license-pubkey.generated.ts from the on-disk pem. Mirrors scripts/bundle-pubkey.mjs pattern for the adapter-registry key. Wired into packages/core/package.json prepublishOnly so every npm publish ships the bundled key in lockstep.
  • packages/core/src/security/license-response-verifier.ts — Ed25519 verifier with strict/transition mode gate (MASSU_REQUIRE_SIGNED_LICENSE=true|unset). Returns tagged union valid | missing_signature | bad_signature | unknown_pubkey | error. Pubkey fingerprint allowlist check rejects bundled keys that don't appear in KNOWN_LICENSE_PUBKEY_FINGERPRINTS.
  • packages/core/src/security/license-pubkey.generated.ts — bundled LICENSE_PUBKEY_ED25519 Uint8Array + LICENSE_PUBKEY_FINGERPRINT_HEX + KNOWN_LICENSE_PUBKEY_FINGERPRINTS Set, all consumed by the verifier.
  • packages/core/src/tests/license-response-signature.test.ts — 6-case drift-guard: bundled-fingerprint allowlist, missing-signature rejection, unsupported-algorithm rejection, garbage-signature rejection, unknown-pubkey rejection, strict-mode env-var read.
  • docs/runbooks/license-response-signing-key-rotation.md — operator runbook for initial provisioning, smoke-test, strict-mode cutover, and rotation procedure.

Fixed

  • packages/core/src/license.ts:280-318validateLicense now calls verifyLicenseResponse(data) after the fetch returns. Under strict mode (MASSU_REQUIRE_SIGNED_LICENSE=true), unsigned/invalid responses throw (caller drops to grace-period cache or free tier). Under transition mode (default), responses are accepted with a one-shot stderr warning per process lifetime so the customer's terminal isn't spammed every session.
  • website/supabase/functions/validate-key/index.ts:7-67 — Edge Function now wraps every successful response with signature / _signature_alg / _signature_payload_keys / _signature_pubkey_fingerprint fields. Canonical-serialization: top-level non--prefixed keys are sorted; JSON.stringify(obj, keysSorted) produces the deterministic input to Ed25519. Signing key is cached per Edge-Function instance for performance. If LICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64 env var unset, responses go out UNSIGNED (transition mode) so the deploy can ship before operator provisions the key.
  • packages/core/package.json:27prepublishOnly extended to also run bundle-license-pubkey.mjs alongside the existing registry-pubkey bundler, ensuring every npm publish ships the freshly-bundled license pubkey.

Operator action required

Try this release

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