- [x] QA'd all new/changed functionality manually
For the following bug:
- https://github.com/fleetdm/fleet/issues/48564
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Style**
* Improved the alignment of an icon within a form field for more
consistent visual layout.
* Updated input text color to a darker tone for better readability and
contrast.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Update the remove_launchctl_service() function to properly handle
wildcard launchctl labels (e.g., 'com.elgato.StreamDeck*'). Wildcard
labels cannot be used directly with 'launchctl list' or as plist
filenames, so they must be expanded to concrete labels first. The
updated function matches the wildcard pattern as a regex against
currently-loaded services and removes each match individually, mirroring
Homebrew's approach. Adds test coverage for this behavior and
regenerates uninstall scripts for affected applications.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved uninstall reliability when apps use wildcard `launchctl`
service labels.
* Uninstall scripts now expand wildcard labels to the set of currently
loaded matching services, remove those services, and delete the related
LaunchAgents/LaunchDaemons plist files.
* If no matching services are found, cleanup exits gracefully.
* Applied to Stream Deck, Fantastical, Company Portal, Krisp,
Pearcleaner, and Wacom Tablet.
* **Tests**
* Added a regression test covering wildcard `launchctl` expansion during
uninstall.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The Homebrew formulae API (served by GitHub Pages) intermittently
returns 5xx errors, which previously aborted the entire ingestion run.
This change adds smart retry logic with exponential backoff for
transient failures (network errors, 5xx/429 responses) while preserving
immediate failure for permanent errors (404, other 4xx).
Implementation:
- Created transientErr wrapper type to distinguish retryable from
permanent failures
- Wrapped fetchCask HTTP logic in retry.Do with configurable interval
and max attempts
- Added comprehensive tests verifying recovery after transient errors,
exhaustion of attempts, and non-retrying of 404s
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved reliability when fetching Homebrew casks by automatically
retrying temporary failures.
* Added smarter handling for rate limits and server/network errors,
while avoiding retries for missing apps.
* Made error messages from failed responses more concise and readable.
* **Tests**
* Added coverage to verify transient retry behavior, retry exhaustion
handling, and that “not found” responses are not retried.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Relates to #48549
The S3 carve cleanup (server/datastore/s3, run by the
cleanups_then_aggregation cron) advanced ListObjectsV2 pagination using
the response's ContinuationToken — an echo of the request token —
instead of NextContinuationToken. On any bucket with more than one page
of objects this looped forever, hanging the entire serial cleanup cron
and stalling every cleanup/aggregation job ordered after it.
Replace the bucket-listing reconciliation with a direct HeadObject probe
per carve, which is exact and independent of listing order or object
counts:
- Only carves older than 24h with a completed upload are reconciled
(mirrors the MySQL carve store's floor; skips in-flight multipart
uploads). A carve is expired only on a definitive not-found; transient
or other probe errors leave it for a future run, so a carve whose object
still exists is never expired.
- Probes run with bounded concurrency; expirations are written in one
batched, retryable UPDATE (new ExpireCarves datastore method) rather
than one per carve.
- The number of carves reconciled per run is capped so a large backlog
drains across runs without any single run making unbounded S3 requests.
Add S3-carve-store-only server settings (the MySQL carve store is
unaffected):
- s3.carves_cleanup_disabled — skip reconciliation entirely
- s3.carves_cleanup_max_per_run — per-run cap (default 1000)
- s3.carves_cleanup_concurrency — concurrent probes (default 32)
Also log the expired count per run and fix the test bucket cleanup
helper to paginate. Adds unit tests (transient-error safety, partial
failure, concurrency) and a MySQL integration test for ExpireCarves.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added new workstation labels for **Team: g-apple-at-work** and **Team:
g-auto-patching**.
* **Changes**
* Updated team label coverage by removing the older **Team: g-mdm** and
**Team: g-software** labels.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves #
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
Unreleased bug, no changes file
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes
## Testing
- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [ ] QA'd all new/changed functionality manually
For unreleased bug fixes in a release candidate, one of:
- [x] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Personal enrollment status is now preserved and updated correctly when
MDM device records change.
* macOS MDM ingestion now keeps the BYOD/personal enrollment flag for
Fleet devices instead of defaulting it away.
* Incoming server URLs continue to have query parameters removed while
still retaining the enrollment status used for processing.
* **Tests**
* Added coverage for personal enrollment updates and macOS ingestion
scenarios, including BYOD and non-BYOD cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
**Related issue:** N/A — internal update to the release-QA issue
template
# Description
Updates the **Release QA** issue template
(`.github/ISSUE_TEMPLATE/release-qa.md`) to match current product group
naming and to tighten the release-critical issue gate.
Changes:
- **Renamed product group sections** to match the working-group names in
`handbook/company/product-groups.md`: **MDM → Apple at Work** and
**Software → Auto Patching**.
- **Moved OS updates and disk encryption checks** out of Security &
Compliance to the platform-owning groups:
- macOS OS updates + disk encryption → **Apple at Work**
- Windows OS updates + disk encryption → **Power to PC**
- Linux disk encryption stays in **Security & Compliance**
- Per-section Fleet Free premium-gating lists updated to follow the
moves.
- **Consolidated the release-critical issue check** into a single "Ready
for release" gate covering both `~unreleased bug` (regressions
introduced during the cycle — the most critical to clear) and `~release
blocker` labels. Every such issue must have a milestone, an owner, and
be moved to "Ready for release" on its product group board before the
release can proceed.
- **Removed non-functional in-issue anchor links.** GitHub only
generates heading anchors when rendering Markdown as a repo
file/wiki/Discussion, not inside issue bodies, so the previous
`#notes`-style links silently did nothing once the template became an
issue.
# Checklist for submitter
- [ ] ~Changes file added for user-visible changes~ — N/A, internal QA
issue template only (no user-visible product change).
## Testing
This is a docs-only change to a GitHub issue template; no code paths are
affected.
- [x] QA'd all new/changed functionality manually (verified rendered
Markdown, section structure, and label-filter links).
Failures:
- WiX: https://github.com/fleetdm/fleet/actions/runs/28500663153
- fleetctl: https://github.com/fleetdm/fleet/actions/runs/28501229768
New runs:
- WiX: https://github.com/fleetdm/fleet/actions/runs/28521840457
- fleetctl: https://github.com/fleetdm/fleet/actions/runs/28521833124
Adds VEX exclusions for false-positive vulnerabilities flagged by the
scheduled Trivy scans of the `fleetdm/wix` and `fleetdm/fleetctl`
images.
**wix**
([run](https://github.com/fleetdm/fleet/actions/runs/28500663153)):
- `CVE-2026-8461` — ffmpeg libs (libavcodec61, libavformat61,
libavutil59, libswresample5); fleetctl does not process media files when
using fleetdm/wix.
- `CVE-2026-55199`, `CVE-2026-55200`, `CVE-2026-7598` — libssh2-1t64;
fleetctl does not establish SSH connections when generating MSI
packages.
**fleetctl**
([run](https://github.com/fleetdm/fleet/actions/runs/28501229768)):
- `CVE-2026-54512`, `CVE-2026-54513` —
com.fasterxml.jackson.core:jackson-databind; fleetctl does not use Java.
All statements are `not_affected` /
`vulnerable_code_not_in_execute_path`, consistent with existing VEX
entries in these directories. The scan workflows auto-glob the VEX
directories, so no workflow changes are needed.
# Checklist for submitter
- [x] QA'd all new/changed functionality manually (validated JSON;
mirrors existing VEX statements picked up by the scan workflow)
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Updates**
* Updated the macOS app entries for Timing and WhatsApp to newer
versions.
* Refreshed the Timing download link and checksum to match the latest
release.
* Adjusted version checks so the apps are recognized correctly when
determining whether they need updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Resolves https://github.com/fleetdm/fleet/issues/48378 (issue found
while working on the Google Workspace IdP integration).
## Summary
Fixes a bug where an IdP user associated with **multiple hosts** only
had IdP host vitals populated on **one** of them.
`maybeAssociateScimUserWithHostMDMIdP` (called when a SCIM/IdP user is
created) matched all hosts whose MDM IdP account corresponds to the
user, but then deliberately linked only `hostIDs[0]` (with a `// TODO:
confirm desired behavior` / "just use the first one"). So when a user is
created *after* the hosts already enrolled — e.g. a directory sync
creating users for people who each have a laptop and a desktop — only
the first host got a `host_scim_user` row, and therefore only that host
received the user's IdP host vitals and profile-variable resends.
The fix links **every** matching host. `associateHostWithScimUser` is
keyed on `host_id` (`INSERT … ON DUPLICATE KEY UPDATE`) and triggers its
own per-host profile resend, so calling it once per host is safe and
idempotent.
This is shared SCIM linking code, so the fix benefits all IdP sources
(Okta/Entra SCIM as well as the Google Workspace directory sync that
surfaced it). Deletes and updates already handled multiple hosts
correctly; only the initial reverse-link was capped.
## Testing
Added `testScimUserCreateAssociatesAllMatchingHosts`
(`server/datastore/mysql/scim_test.go`): two hosts share one MDM IdP
account, then a SCIM user is created — both hosts must resolve to it via
`ScimUserByHostID`. Fails before the fix (host #2 unlinked), passes
after.
**Related issue:** Resolves#48378
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements).
## Testing
- [x] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* SCIM/IdP user provisioning now associates a new SCIM user with **all**
matching hosts, not just the first match.
* Host end-user details (including IdP username/full name) are now
populated consistently on every associated host.
* **Tests**
* Added SCIM integration and datastore regression coverage to ensure
multiple hosts linked to the same IdP account are all associated during
user creation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Updated available installers for several apps, including Firefox,
Visual Studio Code, Git for Windows, OneDrive, Microsoft Teams, and
others.
* Bumped multiple macOS and Windows app entries to newer releases, such
as BlueJ 6.0.0, Notesnook 3.4.2, and ChatWise 26.7.0.
* Improved version detection so installed apps are recognized correctly
against the latest releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#47722
The issue was from a customer running `GET
/api/v1/fleet/hosts?device_mapping=true&page=1&per_page=100&query=<ADDRESS>%40example.com`
on a script in a for loop. This change reduces the impact of the API on
such workflows.
Results from my local load test:
EXPLAIN ANALYZE:
```
┌───────────────────────────────────┬────────────────┬─────────────┬─────────────────────────────────────────────┐
│ │ optimizer cost │ actual time │ device_mapping aggregation │
├───────────────────────────────────┼────────────────┼─────────────┼─────────────────────────────────────────────┤
│ Old (derived-table GROUP BY join) │ ~23,179 │ ~73 ms │ materialized dm derived table, cost ~7,125 │
├───────────────────────────────────┼────────────────┼─────────────┼─────────────────────────────────────────────┤
│ New (correlated subquery) │ ~1,260 │ ~25 ms │ Aggregate … loops=1 (only the returned row) │
└───────────────────────────────────┴────────────────┴─────────────┴─────────────────────────────────────────────┘
```
Tests with 10k hosts:
```
┌───────────────────────────────────┬────────────┬───────────────┬───────┐
│ dataset │ OLD (main) │ NEW (this PR) │ ratio │
├───────────────────────────────────┼────────────┼───────────────┼───────┤
│ 10k hosts × 3 emails (30k rows) │ 4.6s │ 1.1s │ ~4× │
├───────────────────────────────────┼────────────┼───────────────┼───────┤
│ 10k hosts × 30 emails (300k rows) │ 35.9s │ 1.2s │ ~30× │
└───────────────────────────────────┴────────────┴───────────────┴───────┘
```
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
## Testing
- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [x] QA'd all new/changed functionality manually
## What & why
`GET
/api/v1/fleet/hosts?device_mapping=true&page=1&per_page=100&query=<email>`
caused high MySQL **reader** load on instances with ~10k hosts. Each
page load ran an expensive aggregation over the entire `host_emails`
table even though only ~100 rows are returned.
**Root cause:** with `device_mapping=true`, `applyHostFilters` added a
`LEFT JOIN` on a derived table with `GROUP BY host_id` over
`host_emails`. Because of the `GROUP BY`, MySQL must fully materialize
that derived table (aggregating every row for all hosts) before the
outer `WHERE`/`LIMIT 100` can be applied, so the full cost is paid on
every page request regardless of result size. `CountHosts` reused the
same options, materializing the aggregation a **second** time per page
load.
**Fixes (both in `server/datastore/mysql/hosts.go`):**
1. Replaced the derived-table join with a correlated subquery in the
`SELECT` list (only when `opt.DeviceMapping`), so it is evaluated only
for the rows actually returned, each as an indexed lookup on
`idx_host_emails_host_id_email`. This matches the existing
`host_additional` pattern in the same query.
2. Set `opt.DeviceMapping = false` in `CountHosts` — the column is never
selected for counting — mirroring the existing `opt.DisableIssues`
handling.
## Notes
- The composite index `idx_host_emails_host_id_email (host_id, email)`
already exists, so the correlated subquery resolves via an indexed
lookup per returned row.
- `TestHosts` (full suite) passes, including `HostDeviceMapping`,
`CustomHostDeviceMapping`, and `IDPHostDeviceMapping` (the last two
verify the `custom_*` → `custom` and `idp` → `mdm_idp_accounts` source
translation still works through the new subquery).
- Recommend validating with `EXPLAIN ANALYZE` on a ~10k-host dataset
before/after, per the issue. I did not have access to such a dataset.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Improved host list responsiveness when using search filters alongside
device mapping.
* Reduced database load during host listing by retrieving device mapping
more efficiently per host.
* Improved host counting speed by avoiding device-mapping evaluation for
count queries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- #g-mdm => #g-apple-at-work
- #g-software => #g-auto-patching
- Add #g-power-to-pc
- These teams now use ["continuous
flow"](https://fleetdm.com/handbook/company/product-groups#continuous-flow)
instead of scum so updated language from "Current sprint" to "Kanban
board"
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Updated Chrome managed bookmarks for the Engineering section with new
kanban board links.
* Added bookmarks for `#g-apple-at-work`, `#g-auto-patching`, and
`#g-power-to-pc`.
* **Bug Fixes**
* Removed outdated sprint bookmarks from the managed bookmarks list.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated app release metadata so the latest versions are correctly
detected and installed across macOS and Windows.
* Refreshed download links and checksums for several apps, including
Claude, Codex, Gemini, Postman, Thunderbird, VirtualBox, Ollama, and
others.
* Improved version matching for patch detection so upgrade checks
reflect the newest releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Co-authored-by: Allen Houchins <allenhouchins@mac.com>
Relates to #43544Resolves#45901
Relates to #45902 (repo hygiene specifically)
## Summary
Fixes two production bugs in `tools/fleet-mcp` surfaced via dogfood
usage, plus mirrors PR #45513 so the policy-SQL field is available on
this branch.
### Bug A — `fleet=<team>` filter silently dropped on platform/label
calls
`get_endpoints fleet=Workstations platform=windows` returned hosts from
every team. Fleet upstream `/api/v1/fleet/labels/:id/hosts` accepts
`?team_id=` but discards it: `applyHostLabelFilters` in
`server/datastore/mysql/labels.go` reads the RBAC `filter.TeamID`, never
`opt.TeamFilter` parsed from the URL. The MCP now intersects label-path
results client-side by `host.TeamID` via the new
`filterEndpointsByTeamID` helper so the caller's `fleet=` scope is
actually applied. Operational-quirk comment updated to match real
upstream behavior.
### Bug B — `Total` field showed global count regardless of filters
`get_endpoints fleet=Workstations` reported `Total: <global>` even
though the returned slice was correctly team-scoped — `GetHostCount`
accepts no filters. New `GetHostCountWithFilters` mirrors the listing
path's filter dimensions through a shared `buildHostListParams` helper
so list + count stay in lockstep:
- No label/platform →
`/hosts/count?team_id=…&status=…&query=…&policy_id=…&policy_response=…`
(Fleet's count endpoint honors these).
- Label/platform → reuses the listing fan-out's client-side team
intersection (the count endpoint has the same upstream `team_id` bug
when `label_id` is set).
`mcp_tools_hosts.go` `get_endpoints` handler branches on `anyFilter` and
calls the filtered count when any filter dimension is supplied.
### Policy SQL retrieval (mirror of #45513)
The `Policy` struct lacked a `Query` field, so Go's JSON decoder
silently dropped the `query` key from `/api/v1/fleet/global/policies`.
One-line addition; same struct backs both the global path and the
per-team fan-out.
### Repo hygiene
- Sanitized environment-specific values throughout the package (README,
tool descriptions, attribution comments): dogfood URL →
`your-fleet.example.com`; team examples drop emoji prefixes; specific
real CVE IDs swapped for placeholders; `vetted_queries.go` source
attribution now references CIS-8.1 benchmarks instead of a
personal-handle GitHub fork; canonical repo link points at
`fleetdm/fleet/tree/main/tools/fleet-mcp`.
- Re-vendored `osquery_fleet_schema.json` via the `//go:generate`
directive so the embedded snapshot matches the canonical monorepo copy.
- Deleted `fleet_integration_test.go` and `rate_limit_test.go` per repo
policy: production package ships no `_test.go` files.
## Test plan
- [x] `go build ./...` clean
- [x] `go vet ./...` clean
- [x] `gofmt -l .` empty
- [x] Manual smoke via Claude Desktop stdio: `get_endpoints
fleet=<team>` returns `Total == Returned == team's actual host count`
(no longer global)
- [x] Manual smoke: `get_endpoints fleet=<team> platform=windows`
returns hosts whose `team_id` all match the requested fleet (no longer
all-team Windows)
- [x] Manual smoke: `get_policies` response contains a non-empty `query`
field for each policy
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added new osquery tables for system monitoring: Adobe plugins, open
handles, secure boot certificates, and YARA events.
* Added host-count and endpoint counting scoped by filters (team,
platform, status, query, label, and policy).
* **Improvements**
* Enhanced host filtering behavior to correctly apply filter scope when
computing totals.
* Improved label-based host listing responses by optionally populating
per-host label details.
* Updated tool and schema descriptions/examples for clearer query and
CVE guidance.
* **Tests**
* Extended coverage for policy-filtered label host listing and related
behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@nulmete:
- [x] QA'd all new/changed functionality manually
---------
Co-authored-by: Luke Heath <luke@fleetdm.com>
Co-authored-by: nulmete <nicoulmete1@gmail.com>
## Summary
- Replaces the two "Read the whitepaper" CTA buttons on the
`/infrastructure-as-code` page (hero section and bottom CTA) with "Get a
demo" buttons linking to `/contact`
- Matches the CTA pattern used on other Fleet landing pages
(`/device-management`, `/visibility-and-reporting`)
- Removes the `class="btn btn-primary"` in favor of the
`purpose="cta-button"` attribute styling already defined in the page's
LESS file
## Changes
**`website/views/pages/infrastructure-as-code.ejs`**
- Hero CTA: `Read the whitepaper` → `Get a demo` (link changed from
whitepaper URL to `/contact`)
- Bottom CTA: `Read the whitepaper` → `Get a demo` (link changed from
whitepaper URL to `/contact`)
---
Built for [Mike
McNeil](https://fleetdm.slack.com/archives/D0AFASLRHNU/p1782872932958309?thread_ts=1782872908.617559&cid=D0AFASLRHNU)
by [Kilo for Slack](https://kilo.ai/slack)
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Updated package definitions to match the latest releases for several
maintained apps on macOS and Windows, including browsers, productivity
tools, developer tools, and utilities.
* **Bug Fixes**
* Improved version detection so installed apps are identified correctly
as up to date after upgrading.
* Refreshed download links and checksums to match the newest installers.
* **Chores**
* Bumped version metadata across multiple app entries to keep the
catalog current.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
- @noahtalerman: Two checkboxes for free v. premium makes it possible to
have conflicting info in the same story. Discovered during
#g-power-to-pc standup
Changes:
- Added `hideHeaderLinks` local variables on routes for loading pages to
hide the website's header navigation links.
- Removed the gradient on the bottom of the /lp/patch-faster and
/lp/replace-jamf pages.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Simplified several landing pages by removing gradient backgrounds from
key call-to-action/banner areas.
* Updated selected landing pages to hide header links, creating a
cleaner, more focused page layout.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves
https://github.com/fleetdm/confidential/issues/16293
Test failures are not related to this change. They are currently failing
on main.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
* **Bug Fixes**
* Improved consistency when applying Windows configuration profiles in
batch by validating against the latest server MDM state.
* Fixed an issue where a temporary “assume enabled” setting could affect
real configuration updates; it now applies only to dry runs.
* Ensured team profile validation uses the freshly persisted server
state during the same GitOps execution.
* Added a regression test covering Windows MDM “assume enabled” behavior
for dry-run vs real runs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#44629
Test fix only. Test now distinguishes between being connected to Fleet
MDM and being connected but not osquery-enrolled.
[ ] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
* **Tests**
* Improved validation of host configuration nudge behavior by refining
how Fleet MDM connection states are simulated.
* Test scenarios now better cover: enrolled but not connected to Fleet
MDM, and connected to Fleet MDM without enrollment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#48470
I've seen a Linux dev machine become unusable when custom-gcl is
running, so this aims to fix this developer experience issue.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvement**
* Linting now uses a smarter default concurrency limit, reducing CPU
contention during checks.
* The concurrency limit can now be overridden when needed for faster or
lighter runs.
* Incremental Go linting now applies the same concurrency cap
consistently, helping keep local and CI runs more predictable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Relates to #35120
## Summary
Eleven policy queries in the macOS 26 CIS v1.0.0 benchmark fail or
produce incorrect results due to macOS 26 architecture changes. All
fixes are confined to `ee/cis/macos-26/cis-policy-queries.yml`.
| CIS ID | Policy | Root Cause | Change |
|---|---|---|---|
| 2.3.3.1 | Screen Sharing Is Disabled | macOS 26 socket activation:
`com.apple.screensharing` appears in `launchd` table when sharing is
**off**, not on — logic inverted | Replace `launchd WHERE label =
'com.apple.screensharing'` with `listening_ports WHERE port = 5900` |
| 2.3.3.2 | File Sharing Is Disabled | Same socket activation inversion
— `com.apple.smbd` entry behavior reversed | Replace `launchd WHERE
label = 'com.apple.smbd'` with `listening_ports WHERE port = 445` |
| 2.3.3.4 | Remote Login Is Disabled | Same socket activation inversion
— `com.openssh.sshd` entry behavior reversed | Replace `launchd WHERE
label = 'com.openssh.sshd'` with `listening_ports WHERE port = 22` |
| 2.3.3.6 | Remote Apple Events Is Disabled | Same socket activation
inversion — `com.apple.AEServer` entry behavior reversed | Replace
`launchd WHERE label = 'com.apple.AEServer'` with `listening_ports WHERE
port = 3031` |
| 2.3.3.7 | Internet Sharing Is Disabled | `com.apple.nat` plist nests
`Enabled` inside a `NAT` dictionary; `key = 'Enabled'` never matches at
the top level on macOS 26 | Replace `plist` check with
`sharing_preferences WHERE internet_sharing = 1` |
| 2.3.3.10 | Bluetooth Sharing Is Disabled | `com.apple.Bluetooth` plist
does not exist on macOS 26; `preferences` table returns no rows
regardless of sharing state | Replace `preferences` check with
`sharing_preferences WHERE bluetooth_sharing = 1` |
| 2.3.4.2 | Time Machine Volumes Are Encrypted | Previous query checked
`value = 'NotEncrypted'` in the `plist` table which never matches on
macOS 26; macOS 26 changed the `AutoBackup` boolean from `'1'` to
`'true'` | Rewrite to check `preferences WHERE key = 'AutoBackup' AND
value = 'true'` (TM enabled), pass if TM disabled or if an encrypted
volume is mounted |
| 5.2.2 | Password Minimum Length | macOS 26 `pwpolicy` creates
identifier `com.apple.policy.legacy.minChars`; query filters on
`%minLength` which never matches | Rewrite to use
`JSON_EXTRACT(policy_parameters, '$.minimumLength')` with
`policy_identifier LIKE '%minChars'` |
| 5.7 | Admin Cannot Unlock Locked Session | `LIKE
'%authenticate-session-owner%'` matches both
`authenticate-session-owner` (secure) and
`authenticate-session-owner-or-admin` (insecure) — query can never fail
| Add `AND rule NOT LIKE '%authenticate-session-owner-or-admin%'` |
| 5.10 | XProtect Is Running and Updated | `launchd` table on macOS 26
reads plist files from disk, not live service state — both entries
always present regardless of whether XProtect is running | Replace
`launchd` COUNT check with `processes WHERE name IN ('xprotectd',
'XProtectPluginService')` |
| 5.11 | Logging Is Enabled for Sudo | `sudo_info` JSON stores boolean
flags as `null` (sudo -V prints key name with no value); query checks
for `'true'`/`'1'` which never matches | Use `json_type(...) IS NOT
NULL` to detect key presence vs absence |
## Test plan
- [ ] Verify 2.3.3.1: screen sharing OFF → no port 5900 → PASS; screen
sharing ON → port 5900 present → FAIL
- [ ] Verify 2.3.3.2: file sharing OFF → no port 445 → PASS; file
sharing ON → port 445 present → FAIL
- [ ] Verify 2.3.3.4: remote login OFF → no port 22 → PASS; remote login
ON → port 22 present → FAIL
- [ ] Verify 2.3.3.6: remote apple events OFF → no port 3031 → PASS;
events ON → port 3031 present → FAIL
- [ ] Verify 2.3.3.7: internet sharing OFF → PASS; internet sharing ON →
FAIL
- [ ] Verify 2.3.3.10: bluetooth sharing OFF → PASS; bluetooth sharing
ON → FAIL
- [ ] Verify 2.3.4.2: Time Machine disabled → PASS; Time Machine enabled
with unencrypted destination → FAIL; Time Machine enabled with encrypted
destination → PASS
- [ ] Verify 5.2.2: `pwpolicy -setglobalpolicy "minChars=15"` → query
returns row (PASS); `minChars=8` → no row (FAIL)
- [ ] Verify 5.7: `authorizationdb write system.login.screensaver
authenticate-session-owner` → PASS;
`authenticate-session-owner-or-admin` → FAIL
- [ ] Verify 5.10: both `xprotectd` and `XProtectPluginService` running
→ PASS; either absent → FAIL
- [ ] Verify 5.11: `Defaults log_allowed` in sudoers.d → PASS; removed →
FAIL
Queries manually verified against macOS 26.5.1 VM and physical MacBook
Pro via fleetctl live queries — see issue #35120 for full QA notes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated several macOS CIS compliance checks to use more reliable
evidence, improving accuracy for network sharing, remote access, Time
Machine encryption, password policy length, sudo logging, session
security, and XProtect status.
* Tightened session-access validation so administrator login rules are
enforced more consistently.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Marcus Allen <marcus@Marcuss-Work-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
**Related issue:** Resolves#4842
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* IP-based host searches now match both private and public IP addresses.
* Updated the host search box placeholder and tooltip to refer to “IP
address” (instead of “private IP”).
* **Tests**
* Expanded backend coverage to verify matching (and non-matching)
results for both private and public IPs when listing and searching
hosts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Lucas Manuel Rodriguez <lucas@fleetdm.com>
**Related issue:** N/A (tooling change to the `spec-story` Claude Code
skill)
## Summary
Updates the shared `spec-story` skill
(`.claude/skills/spec-story/SKILL.md`) from the original 100-line
version to the evolved, four-stage gated workflow used to spec stories
today. The skill now uses Figma/Slack/Gong MCP tools and can spawn a
verification subagent; these are noted as prerequisites in the skill
README and aren't part of the out-of-box team setup.
### Key changes:
- **Four staged gates** — Understand → Skeleton → Draft → Create, each
pausing for explicit user approval.
- **Figma: find the Ready page by node-id** — don't stop at the cover
(which only links to the issue) or trust the under-reporting page list;
pull the `✅ Ready` page and extract dev notes and tooltip copy verbatim.
- **Decomposition by specialization** — backend / frontend /
fleetctl-GitOps / agent, with cohesion rules and a mandatory
`Documentation and engineering QA` final-gate sub-issue.
- **Deeper sub-issue specs** — exact `file:line` references, code
blocks, negative-space "why" rationale, and grouped conditions of
satisfaction; plus required prior-art research (GitHub, Slack, git
history) and an SME consultation step.
- **Removes all estimation** — no story points or t-shirt sizing, since
Fleet does not estimate sub-tasks.
- **Expanded `allowed-tools`** — adds the git, Figma, and Slack tools
the research stages rely on.
- **Schema-first exploration** — Stage 1.3 now reads
`server/datastore/mysql/schema.sql` and recent `migrations/tables/`
*first*, before grepping code paths (the DB shape dictates everything
downstream); greps specific tables rather than loading the whole dump.
- **Independent verification gate** — new Stage 3.4 spawns a separate
read-only subagent to skeptically re-confirm the draft's concrete claims
against the real sources (file:line refs, `schema.sql` tables/columns,
`datastore.go` methods, `gh` issue/PR numbers, doc-PR field names) plus
decomposition rules and coverage gaps, returning only what fails. Adds
`Agent` to the skill's `allowed-tools`; the old Stage 3 gate is
renumbered 3.5.
- **Stage 4 wiring** — creates sub-issues, wires them as native tasks
off the parent via the sub-issues API, fills in the parent story's
Engineering section, and never sets a milestone on sub-issues.
- **Skill README** — added `.claude/skills/spec-story/README.md`
(human-facing, doesn't load into context) covering invocation, what the
skill already does, the inputs to supply, and a trimmed prompt template.
No product code, APIs, schema, or `fleetd` are affected.
# Checklist for submitter
- [x] QA'd manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated the project’s Go version settings to keep tooling in sync
across all relevant components.
* Running the standard Go update workflow will now apply version updates
to an additional part of the codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved release preparation and publishing reliability, including
more consistent version updates and chart version bumps.
* Streamlined milestone cleanup so release tracking issues are closed
more predictably.
* **Chores**
* Added a faster path to create a QA issue during release workflows.
* Refined cherry-pick handling for release candidates to better track
remaining work.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Bumped Fleet to version **v4.87.1** across Helm, Terraform, and the
npm package.
* Updated deployment image tags so environments using the chart or
infrastructure defaults will pick up the new release.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fixes issues caused by this merge:
https://github.com/fleetdm/fleet/pull/48261.
- [X] QA'd all new/changed functionality manually
## Summary by CodeRabbit
* **Tests**
* Improved server startup test coverage for health checks to better
reflect how an external client reaches the service.
* Added cleanup to restore network settings after server startup tests,
reducing the chance of one test affecting others.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Improved server startup checks to better reflect real external client
behavior during readiness probing.
* Added cleanup so network-blocking settings are restored after test
runs, reducing the chance of one test affecting another.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
**Related issue:** Resolves#47488
Smoke tested a Render blueprint deploy using this branch:
<img width="655" height="216" alt="Screenshot 2026-06-30 at 12 11 25 PM"
src="https://github.com/user-attachments/assets/7f5b6e76-1aa6-4ae6-b96c-9757f7cf2baf"
/>
## What & why
The Render deployment blueprint provisioned its MySQL service from the
external
[`render-examples/mysql`](https://github.com/render-examples/mysql)
repo, whose Dockerfile pins `mysql/mysql-server:8.0.24`. MySQL 8.0.24
does not support nesting a `UNION` inside the right-hand operand of
another `UNION`, so host-detail queries fail with:
> Error 1235 (42000): This version of MySQL doesn't yet support 'nesting
of unions at the right-hand side'
This surfaced via the Vanta integration hitting `GET
/api/latest/fleet/hosts/:id` on a Render deployment.
This PR switches the `fleet-mysql` service to pull the official
`mysql:8.0.44` image directly (`runtime: image`), removing the
dependency on the external repo. 8.0.44 is Fleet's documented minimum
supported MySQL version and is what `docker-compose.yml` already uses
for dev/CI. The official image honors the same `MYSQL_DATABASE` /
`MYSQL_USER` / `MYSQL_PASSWORD` / `MYSQL_ROOT_PASSWORD` env contract, so
the rest of the blueprint is unchanged.
`8.0.44` (latest 8.0) was chosen over `8.4` so existing deployments
upgrade in place from their current 8.0.24 data volume without a
cross-major manual step.
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`.
## Testing
- [x] QA'd all new/changed functionality manually
Verified locally (Docker) that the failing query shape behaves as
expected across versions:
| Query shape | MySQL 8.0.24 | MySQL 8.0.44 |
|---|---|---|
| `SELECT 1 UNION (SELECT 2)` | works | works |
| `SELECT 1 UNION (SELECT 2 UNION SELECT 3)` | **ERROR 1235** | works |
| `(SELECT 1 UNION SELECT 2) UNION (SELECT 3 UNION SELECT 4)` | **ERROR
1235** | works |
`docker-compose.yml` already runs `mysql:8.0.44` with the same env
contract, so the image swap is a drop-in. Remaining validation on a real
Render Blueprint instance: fresh provision health (`/healthz`, `fleet
prepare db`, `hostport` resolution) and in-place upgrade from an
existing 8.0.24 volume.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated the database deployment target to a newer MySQL version,
resolving a Render deployment error related to union nesting.
* Switched the managed database service to use an explicit MySQL 8.0.44
image for more reliable deployments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated maintained app metadata for many Windows and macOS packages to
newer releases.
* Refreshed download links and installer checksums across the catalog.
* Adjusted version-matching rules so installed apps are recognized
correctly after upgrade.
* Included updates for apps such as Docker Desktop, Discord, Notion,
Tailscale, Loom, Cloudflare WARP, and others.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Changes:
- Replaced the expired development license key
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated an internal development license value used in testing
environments. No end-user-facing behavior changed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Relates to #43544
Security review + hardening of the experimental Fleet MCP server. Issues
were reproduced against a live dev Fleet; this PR fixes the MCP-layer
ones.
| Finding | Before → After | Where |
|---|---|---|
| **A** least privilege | No signal about the token's power → startup
`/me` check **refuses any non-API-only token** (fails closed if
unreachable). API-only users can be scoped to specific endpoints/teams
and their token revoked. Who-can-do-what is documented (Fleet RBAC), not
inferred at runtime. | `main.go` `requireAPIOnlyUser`;
`fleet_integration.go` `WhoAmI` |
| **B** rate-limit XFF bypass | Per-IP keyed on spoofable
`X-Forwarded-For` → Removed rate-limiting (assuming Fleet handles this).
| N/A |
| **C** token egress / SSRF | `FLEET_BASE_URL` unchecked → This is
configured at deployment time and not per-request, so no action is
taken. | N/A |
| **D** error-body leak | Raw Fleet JSON (incl. internal `uuid`) →
trimmed, e.g. `Fleet API returned HTTP 409: Resource Already Exists`. |
`fleet_integration.go` `fleetErrMsg` |
| **E** dead code / docs / tests | Removed dead `GetFleetConfig`; README
18→19 / 16→17; added validator/role tests. | `fleet_integration.go`,
`README.md`, `*_test.go` |
| **F** writes auto-runnable | Only the advisory `destructive=true`
annotation → `fleetMCPInstructions` now tells the client to show the SQL
+ targets and confirm before running `run_live_query`. **Advisory, not a
server control.** | `mcp_server.go` |
| **G** abrupt SIGTERM shutdown | `ListenAndServe` blocked with no
signal handling → on SIGTERM the process was killed immediately (**exit
143**), resetting any in-flight connection at once. Now a signal-aware
root context (`signal.NotifyContext`) drains in-flight requests via
`http.Server.Shutdown` (10s cap), logs `shutting down`, and **exits 0**
— and the startup `/me` check + temp-query sweep + stdio loop all honor
it. Matters for Render redeploys (SIGTERM). | `main.go` |
### Key decisions
- **Rate limit: removed**: assuming Fleet server handles this.
- **`fleetMCPInstructions` / `destructive=true` are advisory** — a
prompt-injected or raw client ignores them. The enforceable controls are
the token role and `--disable_tables` flag. Added an explicit
confirm-before-run instruction for `run_live_query` to
`fleetMCPInstructions` as a cross-client complement to the
`destructiveHint=true` annotation -- the annotation only prompts on
clients that honor it, whereas the instruction reaches any client that
forwards server instructions to the model (and raw JSON-RPC callers that
have no approval UI at all). Both are advisory; the real bound on writes
remains the `FLEET_API_KEY`'s Fleet role.
- **Device-side `curl`/`carves` exfil is a Fleet/osquery capability**,
equally reachable via the UI/`fleetctl`/REST — not an MCP bug.
Comprehensive fix is agent `--disable_tables` (separate
Fleet-server/agent issue).
### Residual attack surface (re: *"no new attack vectors"*)
None of these is a vector the MCP invents beyond what Fleet already
exposes:
| Config choice | Residual vector | Mitigation |
|---|---|---|
| Non-API-only / admin token | Leak = arbitrary osquery everywhere |
API-only **required** (refuses to start otherwise); use **observer** for
read-only. As an API-only user it can also be **scoped to specific
endpoints/teams** and its **token revoked** on leak — neither possible
with a UI session token. |
| `run_live_query` via an auto-approving / prompt-injected client |
Arbitrary osquery on currently-online hosts - read/exfil-capable
(`curl`, `curl_certificate`, `file`, `carves`). No data mutation. |
Non-advisory: `FLEET_API_KEY` role - **observer-plus** needed to run
live queries. A "read-only" deployment (**observer** token) removes the
vector entirely; plus agent-side `--disable-tables` to drop exfil
tables. Advisory: `destructiveHint=true` + confirm-before-run
instruction (F) - an auto-approving client ignores these. |
| Live query at all | `curl`/`carves`/`file` device SSRF + exfil | Agent
`--disable_tables` (separate issue) — not an MCP-layer fix |
| Public SSE, no edge | Unauth flood `429`s operators (shared bucket —
fails safe) | We assume Fleet server handles rate-limiting. |
**Bottom line:** with an API-only observer/+ token (now enforced) over
stdio, `ip` mode, or SSE-behind-an-edge, the MCP adds no new attack
vector beyond Fleet's existing live-query capability; the residual
`curl`/`carves` risk is a Fleet-layer concern tracked separately.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Startup now verifies the Fleet API key belongs to an **API-only**
Fleet user
* Added `/healthz` endpoint
* Hardened Fleet base URL validation to prevent token leakage to unsafe
hosts
* Improved rate limiting behavior (429 includes `Retry-After: 1`; per-IP
uses the direct client address host)
* Signal-driven graceful shutdown and stricter `MCP_AUTH_TOKEN` length
checks
* More robust schema fetching by blocking cross-host and overly long
redirect chains
* **Documentation**
* Expanded/clarified write-operation guidance and confirmation
requirements
* **Chores / Tests**
* Updated `.env` docs/ignores, added developer Makefile helpers, and
refreshed Fleet base URL tests
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Adds an end-to-end boot test for `runServeCmd`, the main server entry
point. This is the coverage milestone for #33370: `serve.go` goes from
~7% to ~64%, and `runServeCmd` itself from 0% to ~62%.
The earlier PRs on this issue (#44929, #45343, #45583, #46166, #46421,
#46517, #46742, #46830, #46893, #47151, #47562, #47891) extracted
testable pieces out of `runServeCmd`, but the function itself stayed at
0% — it blocks on an OS signal and wires the entire server together, so
the only way to cover it is to actually boot it. This PR does that.
`TestRunServeCmd` (gated behind `MYSQL_TEST` + `REDIS_TEST`) boots the
full server against a real migrated test MySQL and Redis, waits for
`/healthz`, then cancels the command context to trigger a graceful
shutdown. It covers two paths:
- **Full boot with Apple MDM enabled** — a 32-byte server private key
brings up the Apple MDM protocol services and the host-identity /
conditional-access SCEP setup, so the boot exercises the MDM startup
path as well as the core wiring, cron schedules, and HTTP server.
- **Fail-fast on bad config** — an invalid Redis host-cache
configuration (enabled with a non-positive TTL) aborts startup through
`initFatal` and returns rather than serving, covering the Redis-init
error path and the nil-pool guard.
Beyond coverage, this doubles as a regression net for the ongoing
`runServeCmd` slicing: a future change that breaks startup now fails
this test instead of reaching a release.
**One production change**, in `runServeCmd`'s shutdown `select`: it now
also watches `cmd.Context().Done()`. This is inert in production — the
root command runs via `Execute()` (not `ExecuteContext()`), so
`cmd.Context()` is `context.Background()` and never cancels. Only the
test runs the command with a cancelable context, which is how it shuts
the server down without sending a real signal (a `SIGTERM` would kill
the test binary).
A couple of notes for reviewers:
- The test uses `os.Setenv` (not `t.Setenv`) because the MySQL test
helper marks the test parallel; the boot scenarios run as serial
subtests so the process-global config env doesn't race.
- `runServeCmd` registers metrics with the process-global Prometheus
registry, which can only happen once per process, so there is a single
full boot here; the error-path scenario fails before that registration.
- The test DB is loaded from a schema dump that doesn't mark every data
migration as applied, so the boot runs with
`FLEET_UPGRADES_ALLOW_MISSING_MIGRATIONS=1`.
It adds ~2s to the `cmd/fleet` (`main`) test bundle, which is well off
the CI critical path.
**Related issue:** Refs #33370
# Checklist for submitter
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually (verified locally:
boots to /healthz, graceful shutdown, ~64% serve.go coverage)
- Changes file: not applicable — internal test coverage with no
user-visible behavior change
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved server shutdown handling to stop cleanly when the running
command’s context is canceled, not only on OS signals.
* Added stronger startup validation to fail fast for invalid Redis
host-cache configuration (e.g., non-positive TTL).
* **Tests**
* Added an end-to-end test that boots the server against real
MySQL/Redis, verifies graceful startup/shutdown, and confirms fast-fail
behavior for misconfiguration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#45682
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
#### Before
<img width="1252" height="1027" alt="Screenshot 2026-06-29 at 10 33
15 AM"
src="https://github.com/user-attachments/assets/847ee011-7d2c-4cd2-9882-1508ed77bbd7"
/>
<img width="1248" height="1008" alt="Screenshot 2026-06-29 at 10 33
21 AM"
src="https://github.com/user-attachments/assets/859c2860-5fdb-43f2-8323-af8fc0665ff8"
/>
#### After
<img width="1198" height="819" alt="Screenshot 2026-06-29 at 10 29
25 AM"
src="https://github.com/user-attachments/assets/b96a134a-1271-40f5-99ca-802c7a1fbe10"
/>
<img width="1201" height="804" alt="Screenshot 2026-06-29 at 10 29
29 AM"
src="https://github.com/user-attachments/assets/37c4c262-95ce-4a77-8979-49944e7f2b75"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Content-hashed static assets under `/assets/` (e.g., hashed JS/CSS,
images, fonts) now use long-lived, immutable `Cache-Control` to improve
repeat page loads.
* **Bug Fixes**
* `Cache-Control` is now applied consistently for successful responses
and `304 Not Modified`.
* Non-hashed assets and non-success/error responses correctly avoid
caching via `Cache-Control: no-cache`.
* **Documentation**
* Added a release note explaining the new `Cache-Control` behavior for
hashed assets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->