* fix(fleet-snapshots): gate reads on admin role and encrypt content at rest
Fleet snapshots capture every node's compose.yaml and .env, so the data is
as sensitive as the live stacks. This hardens access and reliability across
the snapshot pipeline.
- Restrict snapshot reads to administrators. GET /api/fleet/snapshots and
/:id now require the admin role, matching create, restore, and delete; the
Fleet "Snapshots" tab and its panel render only for admins. Previously any
authenticated user could enumerate snapshots and read every node's .env.
- Encrypt snapshot file contents at rest with the instance key. Restore and
cloud-archive paths decrypt on read, so cloud archives stay portable and a
database copy no longer exposes stack secrets in plaintext. Rows written
before this change still read back as plaintext.
- Surface partial captures. A stack whose compose file cannot be read or
fetched, or a file over the 1 MB capture cap, is recorded as a warning and
shown on the snapshot instead of being silently dropped, so a snapshot is
never mistaken for complete. Remote .env read errors are now distinguished
from a genuinely absent .env.
Adds route-authz, capture-warning, and encryption round-trip tests; updates
the Fleet-Wide Backups feature docs.
* fix(fleet-snapshots): gate cloud snapshot reads on admin role
The cloud snapshot read routes were guarded by provider/license only, not by
role, while their write counterparts (upload, delete) already required admin
and the Cloud Backup settings surface is admin-only. Because a downloaded
archive contains plaintext compose and .env files, a non-admin could list and
download cloud snapshots and read every node's secrets, the same exposure the
local snapshot reads were just closed against.
- Require admin on GET /api/cloud-backup/snapshots, /status/:id, and
/object/:keyB64/download, matching the local snapshot reads and the
admin-only Cloud Backup settings section.
- When capturing a remote node, treat a 200 response carrying X-Env-Exists:
false as a stack with no .env (matching the local ENOENT path) instead of
storing an empty .env that restore would later write back.
Adds non-admin authorization tests for the cloud read routes and a remote
absent-.env capture test.
Drops `requirePaid` from `POST /api/fleet/update-all` so Community admins
can dispatch bulk node updates. Per-node OTA was already Community-
reachable (admin-only); this completes the move so the full Remote OTA
surface ships at Community.
Frontend mirrors the backend: removes `canBulkUpdate` from
NodeUpdatesSheet so the "Update all (N)" affordance is purely data-
driven on `updatableRemoteCount > 0`.
Docs realigned to drop fence-spec and Skipper-only phrasing on the
Update all bulk action:
- features/licensing.mdx: Community line now lists Remote OTA (per-node
and Update all); Skipper Fleet Actions parenthetical drops "bulk
update all"
- features/remote-updates.mdx: Note rewritten to role-only requirement
- features/fleet-view.mdx: Update all (n) bullet drops the tier clause
- features/overview.mdx: Fleet View and Remote updates blurbs drop the
Skipper/Admiral fences
- operations/upgrade.mdx: Note rephrased without naming tiers
Test coverage:
- fleet.test.ts: tier-gating spec flipped to assert Community access
- fleet-pilot-update.test.ts: bulk-OTA dispatch suite now spies tier
to Community so it doubles as a regression guard
Community tier opened the Fleet Overview drilldown (node card → stack
list → container list) but two read-only metadata endpoints in
fleet.ts kept their requirePaid gate. Stack names still rendered
because they ride on the un-gated /api/fleet/overview payload, but
expanding a stack always re-fetched containers and hit the orphan
gate, surfacing as a "Failed to load containers for X" toast on every
node card on Community.
Drop requirePaid from GET /node/:nodeId/stacks and
GET /node/:nodeId/stacks/:stackName/containers. Both handlers stay
behind authMiddleware, validate inputs, and proxy to the per-node
api_token for remote nodes. Paid mutations (bulk update, scheduled
snapshots, label bulk actions, Fleet Actions cards) keep their gates
in fleetActions.ts. Add positive Community-tier tests in fleet.test.ts
and clear the stale "Requires Skipper or Admiral license" line from
the OpenAPI descriptions.
Realign tier guards to the user-stated philosophy: Community covers
deploy/monitor at scale plus security basics, Skipper adds automation
and advanced fleet management, Admiral keeps enterprise control.
Community now includes:
- Trivy install / uninstall / update from the Settings Hub (admin role)
- CVE suppressions CRUD (admin role; replicates fleet-wide)
- Manual image scan with vuln, secret, and misconfig results
- Stack-config scan, scan comparison
- Manual fleet snapshots: create, list, view, restore, delete
- Per-node Sencho self-update (Check Updates + per-node Update)
- Fleet Overview search, sort, filters, node-card expand, auto-refresh
Stays paid:
- Scan policies with block_on_deploy enforcement (Skipper+)
- SBOM (SPDX, CycloneDX), SARIF export (Skipper+)
- Bulk Update All across the fleet (Skipper+)
- Scheduled snapshot create (now Skipper, was Admiral)
- Trivy auto-update toggle, fleet-wide policy push (Admiral)
The Settings -> Security tab is unhidden by setting the registry tier to
null. The SecuritySection no longer early-returns a PaidGate; the policy
list, Add Policy button, and policy dialogs are wrapped in {isPaid && }.
The Fleet view drops isPaid gates on the Snapshots tab, Check Updates
button, per-node update handlers, OverviewToolbar grid controls, the
NodeCard expand affordance, and the auto-refresh notice. The
NodeUpdatesSheet receives a canBulkUpdate prop and gates the Update All
button on it. useFleetUpdateStatus and useFleetPolling drop their isPaid
guards so polling runs for Community; useFleetOverview drops the isPaid
wrap on the filter and sort path.
Backend route guards are flipped per the matrix above. The scheduler
tick and requireScheduledTaskTier add 'snapshot' to the Skipper+ branch.
Backend test assertions are inverted for the now-Community endpoints
and a positive Skipper-snapshot-task test is added.
Documentation across features/, api-reference/, and operations/ is
updated to reflect the new tier mapping.
Extract duplicated snapshot capture functions from index.ts and
SchedulerService.ts into a shared module (snapshot-capture.ts). Fix
audit log route patterns that used singular 'snapshot' instead of
plural 'snapshots'. Apply design system to FleetSnapshots component
(card styling, strokeWidth, font-mono, tabular-nums, ScrollArea).
Add safePage pagination, loading toast for creation, and fix the
restore dialog race condition with a controlled AlertDialog. Add
diagnostic logging gated behind Developer Mode. Add tests for
restore endpoint, admin role enforcement, and edge cases. Update
docs with troubleshooting section and refresh screenshots.
Add authMiddleware to all 13 fleet endpoints that were previously
accessible without authentication. Add NaN validation for parseInt
params, stackName validation on snapshot restore, and description
length cap on snapshot creation. Clean up updateTracker entries on
node deletion to prevent memory leaks.
Replace hardcoded colors with design system tokens, swap Select for
Combobox, replace overflow-y-auto with ScrollArea, fix card styling
(shadow-card-bevel, border tokens). Fix stale container data by
always refetching on stack expand with a loading guard against
concurrent requests.
Add operational logging for state-changing fleet operations and
diagnostic logging gated behind Developer Mode. Add 20 fleet tests
covering auth enforcement, input validation, tier gating, and
snapshot CRUD lifecycle.