Breeze RMM v0.91.0 — partner-scope SSO for MSP technicians with login-page branding, agent/watchdog version pinning, VPN presence telemetry, and an SSO/agent-policy fixes wave.
Summary
- Auth / SSO — MSP technicians can now sign in to the partner dashboard via a partner-axis SSO provider, and single-partner self-hosted instances can brand the login page (#2194, #2202). Org SSO login — which was silently broken on production-shaped (
breeze_app/RLS) deployments — is fixed and hardened (#2206). - Agents — partners and orgs can pin the agent and watchdog to a specific version with inherit-with-override semantics (#2187), and the heartbeat update gate now honors the effective (partner + org) update policy instead of silently ignoring locked partner settings (#2184).
- Devices — active VPN client presence telemetry from the agent heartbeat (#2185).
- Enrollment keys — expired keys are now auto-purged, with a manual "Delete expired" action and a readable short-code column (#2203).
Added
- Partner-scope SSO + login-page branding — MSP technicians can SSO into the partner dashboard via a partner-axis OIDC provider (separate from customer/org SSO), and single-partner self-hosted instances can show partner branding and an SSO button on the login page (#2194, follow-ups #2202)
- Agent/watchdog version pins — pin the agent and watchdog to a specific version (or "latest") at partner or org level; org pins override partner pins, letting you hold a known-good version fleet-wide or stage a rollout to one org. If a pinned build doesn't exist for a device's platform/arch, the upgrade fails closed rather than falling back to latest (#2187)
- Active VPN client presence telemetry — devices report which VPN clients are present and active, surfaced on the device (#2185)
- Enrollment key auto-purge — expired enrollment keys are purged automatically after a grace period (default 7 days), plus a manual "Delete expired" action and a short-code column replacing the always-"Hidden" KEY column (#2203)
Improved
- Settings navigation — Partner Settings gets a grouped sidebar nav (matching Organization Settings), and both pages get honest save contracts: no fabricated "Saved at" status, confirmation before discarding unsaved edits on tab switch, and failed saves no longer wipe the page (#2204)
- MCP OAuth — new opt-in anonymous Dynamic Client Registration posture (
OAUTH_DCR_ALLOW_ANONYMOUS) so OAuth clients without initial-access-token support — Claude Desktop / claude.ai / ChatGPT — can connect to the Breeze MCP server (#2193)
Fixed
- Org SSO under RLS — org SSO initiation, the
/sso/checkprobe, and the callback identity lookup read through bare DB calls that returned 0 rows under forced RLS, so org SSO login didn't work on production-shaped deployments; returning logins also inserted duplicate identity rows instead of updating. All fixed, with a unique index preventing identity-linking races and SSO tables wired into tenant-delete cascades (#2206) - Agent update policy enforcement — a partner's locked Agent Update Policy / Maintenance Window was shown as locked in the UI but ignored by the heartbeat update gate; the gate now resolves the same effective partner+org merge used everywhere else, and fails closed if the policy lookup errors (#2184)
- API duplicate-key handling — duplicate-SKU catalog imports (and nine other request paths) now return the intended 409 instead of a raw 500; distributor catalog imports no longer hold a pooled DB connection through multi-second AI enrichment calls (#2192)
- Discovery — clearing an asset's Display Name and saving no longer fails with a literal
[object Object]error (#2200)
Security
- SSO hardening — password reset now respects a partner-axis
enforceSSOflag (previously partner staff could bypass SSO enforcement via password reset) (#2202); IP-level rate limiting on SSO login initiation and a unique index against identity-linking races (#2206) - Agents — version-pin resolution and the update gate fail closed on lookup errors instead of falling through to permissive defaults (#2184, #2187)
Self-hosters are encouraged to upgrade.
Self-Hosting / Upgrade Notes
Upgrade. Bump BREEZE_VERSION to 0.91.0, then:
docker compose pull api web && docker compose up -d
(If you build from source, run pnpm install first.)
Database — 4 migrations, idempotent, auto-apply on boot via autoMigrate (unless AUTO_MIGRATE=false):
sso_providersgains a partner axis (nullablepartner_id,org_idbecomes nullable, XOR check) and a new smallpartner_login_brandingtable, both with RLS,- a tiny cleanup + CHECK constraint on
partner_login_branding.accent_color, - a dedupe + unique index on
user_sso_identities(bounded by user count; removes duplicate rows created by the returning-login bug, keeping the freshest per provider/external-id — any dedupes are logged with row counts), devices.active_vpnsnullablejsonbcolumn (metadata-only ADD COLUMN, no rewrite).
No table rewrites or large backfills — no long stall on boot.
No new required environment variables. Three new optional variables:
ENROLLMENT_KEY_PURGE_AFTER_DAYS(default7) — days after expiry before an enrollment key is auto-purged,ENROLLMENT_KEY_CLEANUP_ENABLED(default on) — kill switch for the auto-purge worker,OAUTH_DCR_ALLOW_ANONYMOUS(defaultfalse) — only relevant if you run the MCP OAuth server withOAUTH_DCR_ENABLED=true; allows anonymous DCR so Claude/ChatGPT-style clients can register. If you set any of these, remember Docker Compose only interpolates variables explicitly mapped in theapiserviceenvironment:block —.envalone isn't enough.
Behavior changes.
- Expired enrollment keys are now auto-purged 7 days after expiry by default (cascading their bootstrap tokens and deployment invites). Keys with no expiry are never touched. Opt out with the
ENROLLMENT_KEY_CLEANUP_ENABLEDkill switch (#2203). - Locked partner agent-update policies are now actually enforced at the heartbeat gate. An org under a partner with a locked
Manualpolicy or a restricted maintenance window that was previously (incorrectly) receiving automatic agent updates will stop receiving them after upgrade (#2184). - Org SSO logins that were silently failing on RLS-enforced deployments now work; duplicate SSO identity rows are deduped once at migration time (#2206).
- Login page — when a partner enforces SSO, the password form collapses behind a "Sign in with password instead" toggle for partner-axis users; org users are unaffected (#2202).
No breaking changes.
Full Changelog: v0.90.0...v0.91.0
What's Changed
- docs: sync technical docs for v0.90.0 by @ToddHebebrand in #2188
- fix(agents): heartbeat update gate honors effective (partner+org) update policy (#2123) by @ToddHebebrand in #2184
- feat(devices): active VPN client presence telemetry (#2139) by @ToddHebebrand in #2185
- ci: raise Integration Tests timeout 40 -> 55 min by @ToddHebebrand in #2191
- fix(api): duplicate-key 409s no longer clobbered into 500s; distributor import enrichment runs outside the held transaction by @ToddHebebrand in #2192
- fix(mcp-oauth): allow anonymous DCR so Claude/ChatGPT can connect by @ToddHebebrand in #2193
- feat(agents): partner + org agent/watchdog version pins (#2124) by @ToddHebebrand in #2187
- feat(auth): partner-scope SSO for MSP technicians + login-page branding by @ToddHebebrand in #2194
- fix(sso): post-merge review follow-ups for partner SSO + login branding (#2194) by @ToddHebebrand in #2202
- fix(discovery): Asset Info save fails with "[object Object]" on empty Display Name by @ToddHebebrand in #2200
- feat(enrollment-keys): auto-purge expired keys, delete-expired action, short-code column by @ToddHebebrand in #2203
- feat(web): grouped sidebar nav for Partner + Org settings, honest save contracts by @ToddHebebrand in #2204
- fix(sso): org SSO under RLS on public routes + identity/cascade hardening (#2195) by @ToddHebebrand in #2206
- fix(web): bump no-silent-mutations targeted-set count to 58 by @ToddHebebrand in #2209
Full Changelog: v0.90.0...v0.91.0