Skip to content

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 00:51

Security

  • The Tools API no longer serves anonymous requests. authMiddleware contained
    if (!apiKey) return; — and no key was configured on any documented install path, so
    every route was open, including the three POST /api/v1/executor/* routes that run
    commands. The bypass is deleted. A key is now required on every non-public route;
    /health, /swagger, /swagger/json, /ui and /ui/ stay public (the Docker
    healthcheck depends on /health, and /ui serves only a static shell whose every data
    call is still authenticated).
  • A key is auto-provisioned on first start, so existing installs keep working. The
    precedence chain is the documented one: MASSA_AI_API_KEY env > security.apiKey in
    ~/.config/massa-ai/config.json > a freshly generated 32-byte hex key persisted there.
    The path is logged once at startup; the value never is. The API refuses to start only
    when no key exists and the config file cannot be written. Concurrent cold starts elect
    a single writer through an atomic exclusive-create lock, so every process agrees on the
    key that is actually on disk — re-reading after a write was implemented first and proven
    insufficient, since any read-then-return is invalidated by a later writer.
  • CORS is an explicit allowlist instead of a wildcard. .use(cors()) accepted every
    origin. Origins now come from MASSA_AI_API_CORS_ORIGINS / security.corsOrigins;
    unset means { origin: false, credentials: false }, and * combined with credentials is
    rejected at startup rather than served.
  • The Web UI authenticates itself without a new login surface. /ui receives the key
    in a <meta name="massa-ai-api-key"> tag only when the request's remote address is
    loopback; other callers get the shell plus a configure-access state. Loopback has three
    spellings on this stack (127.0.0.0/8, ::1, ::ffff:127.0.0.1) and all three are
    accepted. Known limitation: a reverse proxy terminating on loopback appears local.
    See docs/web-ui-access.md.
  • Every documented install path now provisions a key, including the lifecycle hook
    binary, which reads config.json directly rather than importing @massa-ai/shared (it
    is fire-and-forget and silent-degrading, so a 401 would have stopped observation capture
    with no visible symptom). Two further defects surfaced here: setup-local-first.sh
    regenerated config.json wholesale and so silently rotated the credential on every
    re-run, and the compose mcp service had neither a volume nor a key, so on a default
    docker compose up it could not learn the key the api service provisioned. Both images
    now set XDG_CONFIG_HOME=/data so config.json lands inside the mounted volume.
  • The inert admin-preservation middleware is removed. It gated six endpoints on a user
    count that was always zero, so it never denied anything. Those endpoints are protected by
    the mandatory key like every other route, and a parameterised test asserts 401 for each.

Changed

  • BREAKING — embedding failures now throw instead of returning random vectors. With no
    reachable provider, EmbeddingService returned Math.random() vectors under a
    NODE_ENV check, and getDimensions() fell back to 384. Those vectors were stored and
    searched as if they were real, so retrieval silently returned nonsense. Both branches now
    throw; store_memory and update_memory return { success: false }, and HyDE degrades
    through the existing QUERY_UNDERSTANDING_UNAVAILABLE signal.

    Action required if you ever ran without a reachable embedding provider. A 384-d
    random vector is indistinguishable from a real embedding after the fact, so there is no
    detector and no repair: re-index affected projects (reindex with force: true) to
    overwrite them. Memories stored during such a window cannot be recovered by re-indexing
    and need to be re-created.

  • Executor responses report the sandbox mode that was actually used.
    MASSA_AI_EXECUTOR_SANDBOX=auto silently degrades to none when no sandbox tool is
    present, and nothing in the response said so. Every execute, execute_file and
    batch_execute result now carries sandboxMode, and one warning per process names the
    missing tool when auto resolves to none. An explicit none warns nothing. The auto
    default and its best-effort fallback are unchanged (AD-007).

Fixed

  • Graph-neighbor search results are scoped to the searched project. memory_edges has
    no project_id, so BFS walks edges globally; the graph stream checked only deleted_at
    before pushing a neighbor's content into the result set, letting a single cross-project
    edge surface another project's memory. The read seam now filters by project, using the
    caller's id unresolved and skipping when it is absent — the exact scope semantics of
    every other read seam the search fuses.
  • A stale-generation retry no longer leaks its managed-run heartbeat. The retry
    returned from inside the try, reaching none of the three teardown sites, so each
    abandoned attempt left a loop renewing a lease its run no longer owned for the life of
    the process. Exhausting all three retries leaked three loops.
  • Namespace-imported callees resolve against their own module first. The resolver's
    documented order was inverted, so import * as Utils from './utils'; Utils.parse(x)
    bound to whichever other file in the project happened to export a top-level parse.
  • Centrality is loaded under the canonical project id. Indexing with a retired project
    alias queried centrality for a project that owns no symbols, so every chunk was written
    with centralityScore: 0 — silently, because 0 is also the legitimate "not computed yet"
    value. Both load sites are fixed, including the incremental reindex path that
    auto-reindex actually takes.
  • An observation written under a retired alias is findable by its canonical id in the
    same tick.
    The synchronous mirror was keyed on the caller's id until the asynchronous
    persist resolved the alias a tick later, so a reader holding the post-rename id missed a
    write that had already returned. A bounded residual remains for an alias this process has
    never resolved — nothing can consult the database synchronously — and it is documented at
    the call site and pinned by a test rather than left implicit.
  • Plugin bundles never appeared in any host's plugins screen. The installers wrote
    MCP entries, hooks, skills and agent files, but no host plugin registry — the thing
    /plugin (Claude Code), /plugins (Codex) and Cursor's plugin list actually read. All
    four hosts reported ✓ installed while showing nothing. apps/claude-plugin/install.sh
    and apps/codex-plugin/install.sh now delegate registration to claude plugin marketplace add + claude plugin install and codex plugin marketplace add + codex plugin add respectively, guarded by a capability probe so a missing or older CLI falls
    back to the previous file-only behaviour and prints the manual command instead of
    failing.
  • Cursor plugin installed to a path Cursor does not scan. The bundle went to
    ~/.cursor/plugins/massa-ai/; Cursor discovers local plugins under
    ~/.cursor/plugins/local/<name>/. Installing now targets the correct directory and
    removes a pre-fix copy so the two cannot both be discovered and double-register hooks.
    This path is derived from Cursor's plugin documentation and is not verified against a
    running Cursor.app
    — unlike the Claude and Codex routes, which were verified
    end-to-end. Please report if Cursor still does not list the plugin.
  • Codex plugin registration escaped a sandboxed install via CODEX_HOME. The Codex
    app exports CODEX_HOME into every shell it spawns, and the codex CLI prefers it over
    $HOME. apps/codex-plugin/install.sh resolves its own paths from $HOME alone, so an
    install run against an overridden $HOME (tests, --target) wrote files into the
    sandbox while registering the marketplace into the real ~/.codex — and an
    --uninstall in a test suite silently removed a live [marketplaces.massa-ai] table.
    The CLI is now pinned to the same root the installer writes. Note the pre-existing
    divergence left in place: the file-copy half still ignores CODEX_HOME entirely.
  • Claude file-route artifacts survived an upgrade. A user who had installed via the
    old file route kept their loose ~/.claude/commands/massa-ai-*.md, subagent files and
    merged settings.json hooks after upgrading, while the newly-registered plugin supplied
    the same three — double-firing every lifecycle event. The installer now removes those
    artifacts when it takes the plugin route.

Added

  • --plugin-source local|copy|auto on scripts/install-harness.sh, mirroring
    --mcp-source. A host plugin registry stores the marketplace root path, so
    registering a live checkout makes the install die when that checkout moves — measured:
    Claude reports failed to load: cache-miss, and codex plugin list then errors for
    every configured marketplace, not just massa-ai. copy materialises a stable
    marketplace root under ~/.config/massa-ai/marketplace; auto picks local in a
    checkout and copy otherwise (npx / published installs). The OpenCode plugin symlink
    honours the same root.
  • Root .cursor-plugin/marketplace.json, the Cursor counterpart to the existing
    .claude-plugin/marketplace.json and .agents/plugins/marketplace.json.
  • MASSA_AI_SKIP_PLUGIN_REGISTRY=1 opt-out, which also pins the file-route test suites so
    their outcome no longer depends on whether the machine running them has the host CLI
    installed.
  • scripts/tests/test-plugin-registry-registration.sh (45 assertions). The CLI-dependent
    scenarios skip rather than fail when claude/codex are absent, which is the case
    on every CI runner — that surface is covered by the capability probe and by manual
    verification, not by CI.