github LanternOps/breeze v0.95.0

latest releases: v0.114.0, v0.113.0, v0.112.0...
2 months ago

Breeze RMM v0.95.0 — an authentication and tenancy hardening release, plus backup profiles, three new locales, and distributor price ingest. This is a deliberate security reset of the auth surface. Schedule a low-traffic maintenance window.

⚠️ Breaking changes / deploy-time invalidations

These fire together the moment the new API boots. Three sign people or systems out; one blocks the upgrade. All are intentional.

  • Every logged-in web/mobile user is signed out. Existing access and refresh tokens lack the new aep/mep/sid claims and are rejected at both doors — no silent-refresh recovery, no kill switch. Everyone re-authenticates. Agents, Helper, portal, and installers use separate verifiers and are unaffected. Confirm you have a working MFA device or recovery codes before upgrading — login is the only way back in.
  • Every MCP/OAuth client must re-authorize. Legacy plaintext refresh tokens are deleted and their grants revoked (one-way hash storage; cannot be backfilled). Access JWTs minted just before deploy stay valid for their ~30-minute TTL.
  • Every M365 ticket mailbox drops to reauth_required (tenant_id + delta_link cleared); a Partner Admin must re-consent each mailbox. Ingestion pauses until then, but mail is not lost — on re-consent, Graph re-enumerates the current inbox and dedupes by provider message id, so gap mail arrives late, not never. SMTP fallback and Mailgun/generic inbound are unaffected. (Note: disabled mailboxes carrying a tenant_id are also flipped to reauth_required.)
  • Production refuses to boot without a DB app-role credential — see Upgrade Notes.

Added

  • Backup profiles — multi-source selection profiles with partner-wide ownership and per-selection job fan-out, plus a redesigned Backup policy tab (#2417, #2415).
  • Spanish, French, and German locales across the web UI (#2451).
  • Device change history — hardware/OS change detection with a per-device tab (#2505, #2509).
  • TD SYNNEX nightly SFTP price & availability ingest (#2458).
  • Native APNs push, replacing the Expo relay — optional and all-or-none (setting any APNS_* value makes the four credentials required) (#2333).
  • Service principals — API keys that outlive a person, with a delegation ceiling (#2514).

Improved

  • Broad SSO/OIDC hardening: default-role ceiling, provider generation gate, session binding, verified-email requirement, SSRF-safe discovery (#2492).
  • Email/recovery/registration hardening — no enumeration oracles, proven-address email change, email-first signup (#2507).
  • Agent worker-pool wedge observability, pprof capture, log-shipper caps (#2408, #2394, #2402).

Fixed

  • Stranded commands — the API no longer claims pending commands into WS frames no shipped agent parsed (surfaced as misleading "no response from agent"). Server-side; existing agents fixed the instant the API deploys (#2412).
  • Windows backups — the agent resolved the backup helper without its .exe suffix, so they never ran (#2504).
  • Undecryptable claimed commands are released back to pending on heartbeat (#2436).

Security

Auth lifecycle epochs, MFA policy/assurance, MCP/OAuth token hashing + revocation, agent→API trust-boundary hardening, and a wave of SR5 site-axis tenant-isolation fixes. Pre-tag hardening in this release also closed:

  • Service-principal scope-delegation ceiling — a principal can no longer be minted with a scope its creator lacks (closed a privilege-escalation → fleet-RCE path via /dev/push) (#2527).
  • CF-Connecting-IP is now opt-in (TRUST_CF_CONNECTING_IP) — a non-Cloudflare reverse proxy no longer lets a client spoof the header to defeat IP allowlists / rate limits (#2528).
  • Email-change verification tokens now carry a user-id RLS branch so org-scoped users can complete an email change (previously 500'd) (#2529); OIDC issuer-clear rejects cleanly (#2533); verify-email failure UX restored (#2534).

Self-hosters are encouraged to upgrade.

Self-Hosting / Upgrade Notes

Upgrade command — bump BREEZE_VERSION=0.95.0 in /opt/breeze/.env, then:

docker compose pull api web && docker compose up -d

(If you build from source, run pnpm install first.)

1. Database — 26 idempotent migrations, auto-apply on boot via autoMigrate. Mostly metadata-only (including 2026-07-20-email-verification-token-user-id-policy — a policy-only broadening so org-scoped users can complete an email change; no data change). Three deserve attention:

  • 2026-07-15-auth-epochs-and-family-expiry.sqlthe one to size first. It backfills absolute_expires_at on refresh_token_families (unbatched UPDATE + SET NOT NULL in one transaction). That table grows with every login session; on a large deployment, run SELECT count(*) FROM refresh_token_families; first — if it's large, expect the migration to take a while (batch the backfill manually before upgrading if needed). Also introduces a 30-day absolute refresh-family lifetime cap (env-tunable via REFRESH_FAMILY_ABSOLUTE_TTL_DAYS).
  • 2026-07-12-drop-file-transfers.sqldestructive: drops the dead file_transfers table + enums (zero code references; never dispatched to an agent). Dump first if you want the rows; the row count is logged on drop.
  • 2026-07-16-td-synnex-sftp-price-file.sql — creates a breeze_search NOLOGIN role and transfers a SECURITY DEFINER function to it (ALTER FUNCTION ... OWNER TO). Fine on the default compose superuser and on DO-managed (doadmin). Aborts the boot only if a breeze_search role already exists under a different owner.

No large-table index builds; no rewrites beyond a small ticket_mailbox_connections.tenant_id type change.

2. New required environment variables.

  • Production now refuses to boot unless one of DATABASE_URL_APP (preferred), BREEZE_APP_DB_PASSWORD, or POSTGRES_PASSWORD is set — this configures the unprivileged breeze_app request role. AUTO_MIGRATE=false no longer skips this check. The API also hard-fails if it detects it is running as a SUPERUSER/BYPASSRLS role (that means RLS was being bypassed on v0.94.0 — after upgrading, rows that were never meant to be visible to a tenant will correctly disappear; that is RLS working, not data loss). Multi-host/HA DATABASE_URL cannot derive the app role — set DATABASE_URL_APP explicitly. A value in .env must also be mapped in the api service environment: block; the shipped compose files now map all three. Runbook: docs/runbooks/request-database-role.md.
  • If you front Breeze with Cloudflare, set TRUST_CF_CONNECTING_IP=true in .env. It defaults off (secure for non-Cloudflare deployments); leaving it off behind Cloudflare means client IPs resolve from X-Forwarded-For instead of CF-Connecting-IP.
  • If you use native APNs push, all four APNS_* credentials are required once any is set.

3. Behavior changes.

  • Backup file-mode exclusion globs begin enforcing end-to-end once you promote the agent fleet — previously-inert exclusions start applying, so backup contents may legitimately shrink.
  • Newly-enforced auth policies that were previously no-ops: SSO email_verified:false rejection (including already-linked users), MFA allowedMethods, API-key creator authorization, requireMfa session gate (partner values now cascade to and lock orgs), portal-ticket enable_tickets gate, SSO default-role provisioning gate. Each is silent and data-dependent — worth querying production before deploying to find who is affected.

Agent fleet: AGENT_AUTO_PROMOTE=false in production — agent-side fixes (incl. the Windows backup helper fix) do not reach devices until you promote the fleet.

Full Changelog: v0.94.0...v0.95.0


What's Changed

  • fix(api): scope event WS ticket to all accessible orgs by default (#2256) by @ToddHebebrand in #2305
  • fix(agent): detect Elastic Defend as antivirus on Linux (#2018) by @ToddHebebrand in #2306
  • feat(devices): linked device profiles v2 — inactive strips, no collapsing (#2138) by @ToddHebebrand in #2309
  • feat(onedrive-helper): Phase 2 — server write path + Graph picker + Windows agent applier by @ToddHebebrand in #2311
  • feat(onedrive-helper): Phase 3 — policy editor tab, library picker, device panel, fleet rollup by @ToddHebebrand in #2318
  • feat(onedrive-helper): Phase 4 — signed-in UPN reporting + Entra graph_group library targeting by @ToddHebebrand in #2322
  • feat: extension seam — gitignored extensions/, namespaced migrations, tenancy registry, runtime loader by @ToddHebebrand in #2324
  • feat(onedrive-helper): Sub-project B phase 1 — stale-mount scrub, multi-account support, Graph cache hardening by @ToddHebebrand in #2332
  • fix(config-policy): accept capability-shape remote access settings again (#2320) by @ToddHebebrand in #2329
  • test(web): fix keystroke-drop flake in PamRuleModal recent-requests preview test by @ToddHebebrand in #2328
  • fix(automations): clamp config-policy subset device resolution to policy partner (#2286) by @ToddHebebrand in #2313
  • fix(api): accept {"stopped":true} in desk-command_result schema (#2307) by @ToddHebebrand in #2312
  • feat(web): collapsible Activity rail on device Overview by @ToddHebebrand in #2304
  • fix(patches): restore third-party Patch Sources toggle in policy Patch tab by @ToddHebebrand in #2300
  • fix(api): readable string-first zValidator 400 bodies via shared wrapper (#2201) by @ToddHebebrand in #2317
  • refactor(quotes): QuoteDepositConfig as a discriminated union (#2248) by @ToddHebebrand in #2316
  • feat(web): surface hidden decommissioned devices on the Devices page (#2251) by @ToddHebebrand in #2315
  • fix(vuln): skip malformed upstream CVE ids instead of aborting the sync (#2261) by @ToddHebebrand in #2314
  • fix(backup): cascade backup rows on config-policy feature-link delete (#2302) by @bdunncompany in #2303
  • Agent backup server URL failover + DNS last-known-good cache (#2288) by @ToddHebebrand in #2323
  • feat(tickets): ticket intake forms — dual-axis custom forms for uniform ticket creation (Phase 1) by @ToddHebebrand in #2339
  • fix(installer): Windows filename-bootstrap token lost when server URL has a nonstandard port (#2341) by @ToddHebebrand in #2342
  • feat(web): internationalize console for Brazilian Portuguese by @ToddHebebrand in #2340
  • feat(abuse): signup abuse detection — attribution capture, hourly signals sweep, ops alerting by @ToddHebebrand in #2343
  • fix(security): enforce site-axis authz in aiToolsFleet handlers (SR5-03/04/05/06/20/22) by @ToddHebebrand in #2349
  • fix(security): site-scope invoice/quote AI actors + services (SR5-14/15) by @ToddHebebrand in #2350
  • fix(security): site-scope backup/DR restore source snapshots (SR5-11/12/13) by @ToddHebebrand in #2351
  • fix(security): site-axis fixes for config-policy assignments, audit arrays, invite + Huntress aggregates (SR5-07/17/18/19) by @ToddHebebrand in #2352
  • fix(security): owner-bind AI session read/approval + redact transcript secrets (SR5-09/10/16) by @ToddHebebrand in #2353
  • fix(security): re-tier AI filesystem read/list as privileged + agent path containment (SR5-01) by @ToddHebebrand in #2354
  • fix(security): bound Google mobile-wipe target + site-scope AI monitor probing (SR5-02/08) by @ToddHebebrand in #2355
  • feat(tickets): intake forms Phase 2 — portal card-grid intake + org-subset allowlist by @ToddHebebrand in #2358
  • fix(web): security dashboard — i18n glue regression, honest error/empty states, actionable alarms by @ToddHebebrand in #2379
  • fix(test-infra): make cleanupDatabase() truncate tenant roots for real (#2205) by @ToddHebebrand in #2330
  • chore: prune stale planning docs + add confidential-info commit guard by @ToddHebebrand in #2337
  • fix(mobile): App Store readiness — deletion URL, Lock action, iOS build number by @ToddHebebrand in #2325
  • fix(security): agent→API trust-boundary hardening (10 findings) by @ToddHebebrand in #2359
  • fix(portal): enforce portal_branding.enable_tickets on all portal ticket surfaces (#2345) by @ToddHebebrand in #2369
  • fix(ai-tools): structured VALIDATION_ERROR for manage_* billing-domain tools (#2362) by @ToddHebebrand in #2370
  • fix(ai): catalog MCP tools — strip raw distributor blob + internal IDs, SKU/part-number search (#2365) by @ToddHebebrand in #2371
  • fix(api): loud rate-limited warning + metric when proxy trust is misconfigured (#2364) by @ToddHebebrand in #2372
  • fix(tickets): ticket-form allowlist edits no longer 400 when an allowlisted org becomes inactive (#2357) by @ToddHebebrand in #2374
  • fix(oauth): MCP refresh invalid_target — normalize resource aliases + 30-min access tokens (#2363) by @ToddHebebrand in #2376
  • fix(web): honor partner session timeout in All Organizations mode (#2347) by @bdunncompany in #2348
  • fix(web): clarify UniFi integration copy across all sections by @ToddHebebrand in #2384
  • Hash-aware per-tab help docs across integrations, device, config-policy & settings by @ToddHebebrand in #2383
  • feat(ai-tools): add get_quote / list_quotes read tools + reject empty update patch (#2361) by @ToddHebebrand in #2373
  • feat(web): i18n follow-ups — intake-forms extraction + cross-island E2E by @ToddHebebrand in #2346
  • feat(devices): vm_host link groups — nest guest VMs under their host server (#2308) by @ToddHebebrand in #2331
  • security(mcp/oauth): close MCP-OAUTH-01..12 — scope policy, RBAC, token hashing, revocation, execution attribution, DCR hardening by @ToddHebebrand in #2377
  • fix(agent): remove filetransfer map entries when HandleTransfer returns (#2388) by @ToddHebebrand in #2391
  • fix(agent): stop heartbeat watchdog from firing every heartbeat on slow links (#2386) by @ToddHebebrand in #2392
  • feat(agent): runtime memory gauges on heartbeat + on-demand pprof capture (#2389) by @ToddHebebrand in #2394
  • fix(agent): bound worker-wedging blockers that retain command payloads (#2387) by @ToddHebebrand in #2395
  • fix(agent): chunk log shipper flushes to the API's 200-entry request cap (#2397) by @ToddHebebrand in #2402
  • fix(agent): collapse macOS log show fan-out, stream-bound output, back off event-log cadence (#2390) by @ToddHebebrand in #2393
  • fix(backup): storage-config UX — require/derive S3 region, edit UI, friendly path validation by @ToddHebebrand in #2406
  • fix(api): cap script timeoutSeconds intake at 3600 to match agent executor clamp (#2398) by @ToddHebebrand in #2403
  • fix(web): FileManager cancel actually aborts the in-flight transfer (#2396) by @ToddHebebrand in #2404
  • fix(agent): reduce WS read limit 64MB → 16MB and align API send-side caps (#2399) by @ToddHebebrand in #2405
  • feat(push): native APNs sender + dispatch, drop Expo push-relay dependency by @ToddHebebrand in #2333
  • feat(ai): org/site MCP tools — list_organizations + manage_organizations (#2366) by @ToddHebebrand in #2375
  • feat(agent): worker-pool wedge observability — heartbeat gauges + short ephemeral watchdog tier (#2400) by @ToddHebebrand in #2408
  • feat(api): expose capture_pprof as MCP tool capture_agent_pprof (#2401) by @ToddHebebrand in #2409
  • feat(auth): authentication lifecycle foundation (SR2-01..04, +SR2-08, +SR2-15 creator check) by @ToddHebebrand in #2378
  • fix(security): harden Microsoft 365 mailbox consent by @ToddHebebrand in #2356
  • fix(oauth): PR #2377 follow-ups — grant revoked_at durability, accurate 503, single RBAC fetch by @ToddHebebrand in #2410
  • feat: extension seam v2 (agent-authenticated extension routes) + generic workspace-index agent module by @ToddHebebrand in #2411
  • chore(remote): remove dead chunked file-transfer subsystem (#2396) by @ToddHebebrand in #2413
  • feat(backup): redesign config-policy Backup tab — four groups, OS presets, destination cards by @ToddHebebrand in #2415
  • fix(web): SSR hydration mismatches + i18n never initialized (two pages rendered raw keys) by @ToddHebebrand in #2416
  • fix(api): stop claiming pending commands into agent WS welcome/heartbeat_ack frames (#2407) by @ToddHebebrand in #2412
  • fix(web): initialize i18n on /register-partner — public signup rendered raw translation keys (#2420) by @ToddHebebrand in #2431
  • fix(api): redact secrets from WS command-result error field before persistence (#2419) by @ToddHebebrand in #2432
  • fix(web): SSR-safe hash-derived tab state — useHashTab hook + the two #2383 regressions (#2421 Phase 1) by @ToddHebebrand in #2433
  • fix(api): release undecryptable claimed commands back to pending on heartbeat delivery (#2414) by @ToddHebebrand in #2436
  • fix(backup): apply file-mode exclusion patterns end-to-end (#2418) by @ToddHebebrand in #2435
  • feat(backup): backup profiles — multi-source selection profiles, partner-wide backup, per-selection job fan-out by @ToddHebebrand in #2417
  • test(agent): de-flake script-runner duration assertion (reds Test Agent on unrelated PRs) by @ToddHebebrand in #2464
  • fix(agent): capture_pprof reports worker-wedge gauges + 30s capture throttle (#2422) by @ToddHebebrand in #2453
  • fix(agent): unifi/workspaceindex follow server-URL promotion + workspaceindex hardening (#2423, #2425) by @ToddHebebrand in #2454
  • fix(web): hash-in-useState hydration sweep — 23 components to useHashState + AST guard test (#2421 Phase 2) by @ToddHebebrand in #2455
  • fix(web): gate row-menu Run Script on decommissioned (not offline) (#2426) by @ToddHebebrand in #2457
  • feat(extensions): verification tripwires for the extension trust boundaries (#2424) by @ToddHebebrand in #2459
  • feat(api): persist and surface vuln-sync malformed-CVE skip counts (#2427) by @ToddHebebrand in #2460
  • fix(api): redact agent-supplied error/output strings on every persistence surface (#2434) by @ToddHebebrand in #2462
  • chore: remove dead web RegisterPage/RegisterForm and agent-local backup scheduler (#2452) by @ToddHebebrand in #2481
  • fix(api): validate backup exclusion globs against the agent's real matcher (#2473) by @ToddHebebrand in #2479
  • fix(agent): log shipper follows backup-server-URL promotion (#2463) by @ToddHebebrand in #2477
  • fix(extensions): derive the extension RLS tripwire's table set from the catalog, not the manifest (#2466) by @ToddHebebrand in #2480
  • fix(api): count kev_epss drops and escalate a feed that never reports a total (#2470) by @ToddHebebrand in #2475
  • fix(web): gate bulk Run Script/Reboot on decommissioned, not offline (#2465) by @ToddHebebrand in #2476
  • fix(web): polish batch — error/empty states, mutation-guard coverage, confirmation, 403 handling (#2429) by @ToddHebebrand in #2471
  • fix(web): finish the 403-vs-transient sweep across the security pages (#2472) by @ToddHebebrand in #2482
  • chore(deps): bump react-native-worklets from 0.10.1 to 0.10.2 in the react-native-animation group across 1 directory by @dependabot[bot] in #2437
  • chore(deps): bump typescript from 6.0.3 to 7.0.2 in /e2e-tests by @dependabot[bot] in #2440
  • chore(deps): bump @types/node from 26.1.0 to 26.1.1 in /e2e-tests by @dependabot[bot] in #2442
  • chore(deps): bump @anthropic-ai/sdk from 0.110.0 to 0.111.0 in /e2e-tests by @dependabot[bot] in #2443
  • chore(deps): bump @anthropic-ai/sdk from 0.105.0 to 0.111.0 by @dependabot[bot] in #2448
  • chore(deps): bump react-native-screens from 4.25.2 to 4.26.0 in the mobile group across 1 directory by @dependabot[bot] in #2438
  • chore(deps): bump the typescript-tooling group across 1 directory with 2 updates by @dependabot[bot] in #2439
  • chore(deps): bump tsx from 4.23.0 to 4.23.1 in /e2e-tests by @dependabot[bot] in #2444
  • chore(deps): bump hono from 4.12.28 to 4.12.30 in the hono group across 1 directory by @dependabot[bot] in #2446
  • chore(deps): bump @typescript-eslint/parser from 8.57.1 to 8.64.0 by @dependabot[bot] in #2449
  • chore(deps): bump @simplewebauthn/server from 13.3.1 to 13.3.2 by @dependabot[bot] in #2450
  • chore(deps): bump astro from 7.0.6 to 7.0.9 in the astro group across 1 directory by @dependabot[bot] in #2441
  • chore(deps): bump eslint from 10.6.0 to 10.7.0 in the linting group across 1 directory by @dependabot[bot] in #2447
  • feat(auth): MFA policy and assurance (SR2-05,06,07,09,19,20,24) by @ToddHebebrand in #2385
  • fix(db): enforce effective request database role (SR1-02) by @ToddHebebrand in #2368
  • feat(i18n): add Spanish, French, and German locales by @ToddHebebrand in #2451
  • feat(catalog): TD SYNNEX nightly SFTP price & availability file ingest by @ToddHebebrand in #2458
  • fix(i18n): TD SYNNEX locale parity (fix main-red) by @ToddHebebrand in #2491
  • fix(auth): wire email_epoch — email change revokes sessions, MCP grants, and stale verification links (#2428) by @ToddHebebrand in #2461
  • feat(web): device Change History tab (Phase 1 of #2502) by @ToddHebebrand in #2505
  • ci(rls): gate the RLS session-context contract suite — it was running in no job at all by @ToddHebebrand in #2493
  • fix(sso): normalize blank issuer/defaultRoleId to NULL instead of 400ing by @ToddHebebrand in #2494
  • feat(agent,api,web): hardware & OS change detection (Phase 2 of #2502) by @ToddHebebrand in #2509
  • feat(sso): OIDC hardening — default-role ceiling, provider generation gate, session binding, verified-email requirement, SSRF-safe discovery (SR2-10..14) by @ToddHebebrand in #2492
  • Security: MCP org API-key site-scope fails closed on null creator perms (SR2-15) by @ToddHebebrand in #2510
  • feat(auth): email/recovery/registration hardening — no enumeration oracles, proven-address email change, email-first signup (SR2-17,18,21,22,23) by @ToddHebebrand in #2507
  • fix(ci): replace pnpm audit with osv-scanner — npm retired the audit endpoints by @ToddHebebrand in #2515
  • docs: recognize Breeze sponsors and contributors by @ToddHebebrand in #2508
  • fix(agent): resolve breeze-backup helper as breeze-backup.exe on Windows by @ToddHebebrand in #2504
  • chore(deps): bump the aws-sdk group in /agent with 4 updates by @dependabot[bot] in #2496
  • chore(deps): bump google.golang.org/api from 0.287.0 to 0.288.0 in /agent by @dependabot[bot] in #2500
  • chore(deps): bump golang.org/x/net from 0.56.0 to 0.57.0 in /agent by @dependabot[bot] in #2499
  • chore(deps): bump github.com/getsentry/sentry-go from 0.47.0 to 0.48.0 in /agent by @dependabot[bot] in #2498
  • chore(deps): bump postcss from 8.5.16 to 8.5.19 in the tailwind group across 1 directory by @dependabot[bot] in #2445
  • feat(api-keys): live creator authorization + service principals — a key cannot outlive its creator's authority (SR2-15) by @ToddHebebrand in #2514
  • fix(security): trusted client-IP boundary — allowlists fail closed, rate limits stop trusting spoofable headers (SR2-16) by @ToddHebebrand in #2516
  • docs: add cascade registration to the new-tenant-table checklist by @ToddHebebrand in #2521
  • fix(setup): prevent guided setup exit in default directory by @ToddHebebrand in #2519
  • fix(deploy): map POSTGRES_PASSWORD and APNS_* into the api service env by @ToddHebebrand in #2522
  • fix(deploy): map DATABASE_URL_APP into the base compose api service by @ToddHebebrand in #2526
  • fix(security): enforce scope-delegation ceiling on service principals (SR2-15) by @ToddHebebrand in #2527
  • fix(security): only trust CF-Connecting-IP when explicitly behind Cloudflare (SR2-16) by @ToddHebebrand in #2528
  • fix(sso): reject clearing an OIDC provider's issuer with a clear 400 (#2494) by @ToddHebebrand in #2533
  • fix(web): restore VerifyEmailPage failure UX after the enumeration fix (#2507) by @ToddHebebrand in #2534
  • fix(auth): mint email-change token outside the caller's RLS context (#2507) by @ToddHebebrand in #2529

Full Changelog: v0.94.0...v0.95.0

Don't miss a new breeze release

NewReleases is sending notifications on new releases.