Arcade 1.9.7
App version c4c7bc1 · released 2026-08-17 · previous 1.9.6
Release notes
This release lets every PostgreSQL consumer in the chart run against its own server with its own credentials, adds a declarative gateway you can bootstrap from config, and closes the remaining unguarded outbound-dial paths found in the July penetration test. It refreshes the bundled application images (appVersion a2b588e -> c4c7bc1) and applies one engine schema change.
If you use the Snowflake toolkit, upgrade: every one of its tool calls has been failing, and the fix is in this release.
Read the upgrade notes before rolling. Four changes need a decision from you: the coordinator database name is now honored rather than ignored, the plugin webhook SSRF gate ships off and must be narrowed, tenant-created OAuth providers can no longer dial private addresses, and execution-history payloads are now admin-only.
Database migrations
This release applies one engine schema change. Migrations run automatically on helm upgrade unless engine.database.migrations is false. No coordinator or experience-api schema changes ship in this release.
- Engine — enforces one credential per remote MCP server and records, at the point an authorization starts, whether it claims the server's own credential. Required: a remote MCP server owning its credential — and the revoke control in the server list — does not work until it applies.
Rollback is not clean. A server already carrying more than one default credential is migrated by demoting the extras, keeping the earliest authorization. Downgrading the chart drops the index and the column but does not restore the demoted credentials — those users re-authorize. If you run with engine.database.migrations: false, apply the schema change out of band before rolling the new image.
Security
- Tenant-created OAuth provider endpoints and MCP discovery dials now go through the egress guard. Previously any tenant admin who could create an auth provider could point it at an internal address and make the platform connect there — carrying an
Authorizationheader on the user-info and introspection hops. - The OIDC token endpoint taken from a custom user source's discovery document is now dialed through the egress guard. The document is attacker-controlled for a tenant-supplied issuer, so this hop was reachable at an internal address.
- Plugin webhook hooks are gated behind an allowlist and must use
https. Previously registration validated only URL shape, so a hook pointed at a cloud metadata address was dialed from inside the trusted network. - Worker and MCP-server registration refuses private, loopback, link-local, unspecified, reserved, and multicast destinations, and requires an
http/httpsscheme on the server URI and both authorization URLs. - A gateway can no longer forward the connecting client's Arcade credential to its worker:
Authorizationis refused as a passthrough header at write time, case-insensitively. Arcade_ListAppsis scoped to the calling gateway. A gateway restricted to one toolkit previously saw every OAuth app connected anywhere in the project, leaking the identity of services it has no way to reach.- Project and organization names reject control characters, closing a stored prompt-injection path where a name carrying a line break reached a privileged agent's context as a second line the dashboard never showed.
- The playground assistant is told that values inside a tool result are data, not instructions, so content returned by a tool cannot direct it.
- Execution-history inputs and outputs require admin authority; end-user delegated callers are refused the surface.
- Stripe client secrets no longer reach a browser URL, an access log, or a telemetry sink.
- Deployment create and update are rate limited per tenant, and deployment updates emit an audit event with environment values and secrets pinned out of the payload. Both apply to deployments installs — those enabling
deployments:throughengine.extraConfig. The audit event needs no configuration; the rate limit does (see upgrade notes). The checkout page mints its session in place, card and 3DS confirmations resolve without a redirect, and both analytics sinks scrub the secret before transmitting regardless of initialization order.
Resolved CVEs
Advisories remediated in the bundled application images since a2b588e:
- CVE-2026-33818 — Go standard library 1.26.6
- CVE-2026-56853 — Go standard library 1.26.6
- CVE-2026-56859 — Go standard library 1.26.6
- CVE-2026-56862 — Go standard library 1.26.6
- CVE-2026-56864 —
golang.org/x/mod0.40.0 - CVE-2026-56865 —
golang.org/x/mod0.40.0 - CVE-2026-69248 —
cryptography50.0.0 across worker toolkits - CVE-2026-69249 —
cryptography50.0.0 across worker toolkits - CVE-2026-71554 —
h24.4.1 across worker toolkits
Added
- Per-service PostgreSQL endpoints. Each of the chart's four database consumers —
engine,coordinator,experienceApi,rbac.openfga— acceptshost,port,sslmode,sslRootCert,targetSessionAttrs, and anauthblock under itsdatabasekey, alongside the existingname,create, andmigrations. Every key inherits the globalpostgresvalue when unset, so a default install is unchanged. Credentials come fromauth.passwordorauth.existingSecret. A misconfiguration fails the render rather than a pod: a DSN pasted into a host field, a non-integer port, an empty resolved host, bothexistingSecretandpassword, or a multi-host list for OpenFGA. engine.gatewayBootstrap.enabled— runs a post-install and post-upgrade job that reconciles a fixed, curated MCP gateway from the engine's owngatewaysconfig block, so a deployment can expose one without tying it to a project's bindings. Config is the source of truth and is reapplied on every start; an entry set toinactivestops serving without being removed. Off by default. The chart has no value for thegatewaysblock itself — supply it throughengine.extraConfig. Reconciliation touches only gateways this mechanism created: enabling the switch without declaring a gateway does nothing on a deployment that never declared one, and removes the declared gateway on one that did. Gateways created through the dashboard or the API are never in scope.engine.pluginSsrfAllowlistandengine.authSsrfAllowlist— destinations exempted from the private-IP block applied to plugin webhook hooks and to tenant-created OAuth provider endpoints. Entries accept an absolute URL, a wildcard host, a CIDR block, or'*'.engine.deniedPassthroughHeaders— header names a gateway may not declare inallowed_passthrough_headers. Defaults toAuthorization.- A remote MCP server owns one credential of its own. The server list names who authorized it and offers to clear it, so a server whose credential has lapsed — or whose owner has left — is no longer stuck with it. Revoking is offered whenever the server holds a credential, not only while that credential still works.
- Rate limits are a first-class contextual-access policy, settable from the dashboard or the plugin API. Rules match an exact qualified tool name, a toolkit wildcard, or everything, and carry a limit, a time unit, and a per-rule choice of whether to allow calls when the limit cannot be verified. Windows align to the wall clock and rejections carry a retry-after; the default is to reject when the limit cannot be verified.
- The capability-gap and manage-authorization built-in tools are served on the stateless MCP path, which previously offered only the gateway's allow list.
- Tools listings carry
index_stateandlast_indexed_atper tool, so a project owner can tell a tool that is searchable from one that is registered but not yet indexed — replacing the invisible "I added the tool but the agent cannot find it" failure. - Auth-provider metadata carries
status, so a project member can tell an active provider from a configured-but-disabled one without the admin-only read. - Google Drive, Docs, Sheets, and Slides toolkits select their Drive scope breadth by deployment, through the
ARCADE_GOOGLE_DRIVE_SCOPE_MODEenvironment variable on the worker. Unset (the default) keeps the narrow per-file scope. Set it tofull— viaworker.extraEnv, as the chart has no dedicated value — to request broad Drive scopes, so agents can reach files a colleague shared with the user instead of needing each one granted individually. Google treats the broad scopes as restricted, sofullworks only on a deployment whose own Google OAuth application is verified for them.
Changed
- Gateway allow lists accept up to 5000 tools, raised from 500.
- Gateway requirements are read from a single source, so project members now see the same requirement status admins do instead of a partial checklist, and the list paints before the tool catalog finishes loading.
SelectToolsandSearchToolsreject malformed input up front with a descriptive error — an empty or blank task list, a blank query, or a non-positivetop_k— instead of surfacing an internal error after the request reaches the recommendation service.- The manage-authorization tool description states what it actually does: authorization is per tool, not per service, so batching the tools a job needs collapses what would otherwise be consecutive consent screens into one.
- Onboarding's "set up your own" path uses the full gateway create form, with inline secret and OAuth setup, rendered in place rather than in a modal.
Fixed
- An authorization interrupted by a pod restart now completes. A replacement instance silently dropped any pending authorization belonging to a dynamically-created provider, and the caller waited until its own timeout. Authorizations whose provider has since been deactivated are logged and skipped rather than resumed.
- Registering or updating one MCP server no longer blanks every other server from the project's list until the cache lifetime elapses.
- A remote MCP server's health, tool list, and listing entry are read with the server's own credential rather than the viewer's. Any viewer who was not the authorizing operator previously saw an unhealthy marker and an empty tools tab on a server the same page reported as authorized, and the revoke control was offered against the wrong credential.
- A remote MCP authorization reports complete only after its credential and the server's ownership record have both committed. The completion signal previously landed while both writes were still outstanding.
- An unusable remote MCP credential can be replaced. Once a stored credential stopped being renewable, the authorize action became a permanent no-op — a success message on a server that stayed unusable, with no way back.
- Two crashes in the remote MCP authorization path are fixed: completing a flow for a server configured with manual OAuth client credentials, and handling a token response that omits
expires_in. - A malformed worker-create request is refused with 400 instead of returning 500.
- A
tools/callon the stateless MCP path matches the advertised tool name regardless of the casing stored in the gateway's allow list. A gateway configured with a lowercased tool name advertised the canonical spelling and then refused it. - A deployment log stream that goes quiet reaches its client instead of failing with 500.
- Popup-based authorization keeps its opener, so the application that started the flow receives the result. A security header sent on every response was severing the reference during the redirect chain.
- Signing in no longer lands on an empty dashboard when the default organization has no reachable project — the remaining organizations are tried in turn.
- The contextual-access page renders for a project admin who is not an organization admin, instead of failing the whole page on an org-scoped read they cannot perform.
- A user who belongs to an organization but holds no project can see and accept a pending project invitation instead of dead-ending on an empty state.
- Navigation is gated on the permission behind each destination, so members see counts and read-only information with a hint to ask an admin rather than links that lead to a permission error.
- The connected-users list refreshes after a verification test, a server authorization, or a playground tool authorization, instead of holding stale data until a manual reload.
- An expired session in the playground chat history triggers re-authentication instead of replacing the sidebar with an error state.
GoogleCalendar.UpdateEventrefuses to edit an event the user does not organize and cannot edit as a guest, instead of silently writing to the user's private copy and leaving every other attendee on the original time.Linear_GetIssuereturns inbound relations, so asking what an issue is blocked by returns the blocking issues rather than nothing.- Linear date-accepting tools reject a malformed
YYYY-MM-DDvalue up front, naming the field, instead of forwarding it and surfacing an opaque upstream error. Linear_ListLabelsandLinear_ListWorkflowStatesare paginable, so workspaces with more labels or states than one page are no longer silently truncated.Linear_CreateIssueRelationreports whether it created a new relation or found an identical one already present.- Every Snowflake tool call failed. The toolkit resolved its database connector to a prerelease whose rewritten core rejects the integer session-parameter values the toolkit sends, so the connection raised before any network call and every tool returned a retryable error naming no statement. The connector is now held below that rewrite and session parameter values are sent as strings.
- Snowflake query failures that fall through error classification name the failing statement, instead of reporting only that the query failed.
- Confluence search names the parameters it needs when called with empty search input.
- The Microsoft SharePoint and Microsoft Power BI toolkits are servable. Both loaded cleanly but could not be started — one shipped no console scripts, the other no module entry point.
Removed
- The unused
/admin/catalog/gatewaysendpoint.
Operator impact
- New values: per-service
databaseendpoint keys onengine,coordinator,experienceApi, andrbac.openfga;engine.gatewayBootstrap.enabled;engine.pluginSsrfAllowlist;engine.authSsrfAllowlist;engine.deniedPassthroughHeaders. - Set through an escape hatch, not a dedicated value: the
gatewaysblock thatengine.gatewayBootstrap.enabledreconciles from goes inengine.extraConfig, as does thedeployments:block and its newrate_limit;ARCADE_GOOGLE_DRIVE_SCOPE_MODEgoes inworker.extraEnv. - Requires a decision:
coordinator.database.nameother thanarcadenow fails the render;engine.pluginSsrfAllowlistships permissive and should be narrowed. - Secrets: per-service database credentials may come from an existing secret via
database.auth.existingSecret. No existing secret changes shape. - RBAC: unchanged in the chart. In the application, execution-history payloads move to admin authority and dashboard navigation is gated per permission.
- Kubernetes version: unchanged,
>= 1.30.0. - Restarts: engine, coordinator, and OpenFGA pods roll once because
DB_DATABASEmoves the PostgreSQL config checksum.
API & behavior
- Execution-history detail endpoints omit inputs and outputs for non-admin callers and refuse end-user-delegated callers entirely.
- Gateway create, update, and patch reject
Authorizationinallowed_passthrough_headerswith a 400. - Gateway allow lists accept up to 5000 entries.
- Worker create rejects a malformed body with 400, and refuses a destination that resolves to a private or otherwise non-public address.
- Tools listings gain
index_stateandlast_indexed_at; auth-provider metadata gainsstatus. - Plugins accept
plugin_type: rate_limitwith arate_limit_configblock;GET /pluginsround-trips it. SelectToolsandSearchToolsreject malformed input with a typed rejection — HTTP 400 over REST, invalid-params over MCP.Arcade_ListAppsreturns only apps backing a tool the calling gateway can reach.- The capability-gap and manage-authorization built-ins are listed and callable on the stateless MCP path.
/admin/catalog/gatewaysis removed.