Resolves#41985
Fixed issue were hosts migrated from another MDM via the macOS Tahoe
end-user authentication flow had only the IdP email populated on host
details because the SCIM user mapping was never created at
OTA-enrollment ingest time.
Resolves#45220 (one of many PRs)
## Testing
- [X] QA'd all new/changed functionality manually.
Smoke tested upload/download of software installers, upload/download of
bootstrap packages.
**Related issue:** Resolves#45220 (one of many PRs)
## Testing
- [X] QA'd all new/changed functionality manually.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Test utilities moved into test-only files and removed from production
code, reducing runtime dependencies and tightening module boundaries.
* **Tests**
* Updated test setup to use internal test helpers and explicit Redis
test setup, improving test isolation and reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes#43609 and #43566
Fixed bug were toggling GitOps mode was creating a spurious
update_conditional_access_bypass activity whenever Okta conditional
access was already configured with bypass_disabled=true.
**Related issue:** Resolves#44459
## Summary
Fixed an issue where the recovery lock password for a macOS host could
become unretrievable via the UI and API after the host was transferred
to a team with recovery lock disabled, even though the password was
still stored in the database.
## Root causes
Three bugs combined to produce the symptom:
- **Frontend visibility**: `canShowRecoveryLockPassword` gated the "Show
Recovery Lock password" action on the current team's
`enable_recovery_lock_password` setting and ignored the
`recoveryLockPasswordAvailable`
signal, so the UI option vanished whenever the host's new team had the
feature off.
- **API 404 on view**: `MarkRecoveryLockPasswordViewed` returned a
`notFound` error when its UPDATE matched zero rows. This happened as
soon as the `ClaimHostsForRecoveryLockClear` cron flipped the password
row's
`operation_type` from `install` to `remove` — causing `GET
/hosts/:id/recovery_lock_password` to 404 even though the password had
already been successfully retrieved and decrypted moments earlier in the
same
request.
- **Stale rotation deadline**: `ClaimHostsForRecoveryLockClear` left the
row's `auto_rotate_at` untouched when flipping to `remove`, so
subsequent reads still surfaced the pre-transfer view-deadline and the
UI
rendered a rotation banner promising an auto-rotation that the cron
(filtered on `operation_type='install'`) would never honor.
## Fix
- **Frontend**: updated `canShowRecoveryLockPassword` to also show the
action when a password is available, regardless of the team setting.
- **API**: changed `MarkRecoveryLockPasswordViewed` to return a zero
`time.Time` with no error when no install-state row exists. The service
skips the `AutoRotateAt` assignment in that case and explicitly nulls
any stale value loaded from the DB, so the response's `auto_rotate_at`
is omitted.
- **Data hygiene**: updated `ClaimHostsForRecoveryLockClear` to also set
`auto_rotate_at = NULL` when flipping a row to `remove`, since the
rotation deadline is meaningful only for install-state rows.
- [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.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#45230, Resolves#45213
# Checklist for submitter
- [ ] 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
#### For 45213
Did a gitops run to apply external URLs as logos:
<img width="395" height="122" alt="Screenshot 2026-05-12 at 10 52 25 AM"
src="https://github.com/user-attachments/assets/a1fea9ce-7a3d-419b-8c56-68568dcc704e"
/>
Command: `./build/fleetctl gitops -f
/Users/nico/dev/gitops-output-test/default.yml` (**gitops-output-test**
is where I usually have my gitops outputs).
Then I deleted both from the UI:
https://github.com/user-attachments/assets/03899795-7cda-485d-b87e-25f829b928b7
#### For 45230
- Uploaded logos using **org_logo_path_dark_mode** and
**org_logo_path_light_mode** in the first GitOps run.
- In the second GitOps run, set **org_logo_url_dark_mode:
"https://placehold.co/100"** and **org_logo_url_light_mode:
"https://placehold.co/100"**.
https://github.com/user-attachments/assets/4dfd0440-9a17-44e1-aa7d-395afd9c7d7a
For unreleased bug fixes in a release candidate, one of:
- [x] Confirmed that the fix is not expected to adversely impact load
test results
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Organization logo deletion is idempotent and no longer errors on
repeated deletes.
* Orphaned hosted logo blobs are cleaned up after config changes;
deletion or activity-emission failures are logged and do not abort
requests.
* Conflicting/contradictory URL updates now produce validation errors.
* **Improvements**
* GitOps/logo sync strips gitops-only path keys and only performs
uploads in non-dry-run flows.
* Logo URL handling simplified: deprecated alias fields are mirrored and
path keys are stripped to avoid unintended deletions.
* **Tests**
* Added lifecycle tests for upload/delete, activity emissions, and
in-memory PNG generation.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45236)
<!-- review_stack_entry_end -->
<!-- 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#45163
# Details
Limits CVE data collection to only those CVEs which we report on in the
chart. This is a performance optimization necessitated by the large
amount of data that bigger fleets may generate. The plan is to implement
a data compression strategy so that we can go back to collecting full
CVE data soon.
# 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.
n/a, unreleased
- [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] QA'd all new/changed functionality manually
- [X] Ran some collection jobs and verified that only tracked CVEs were
represented in "open" rows.
- [ ] Ran load test w/ new code
For unreleased bug fixes in a release candidate, one of:
- [ ] Confirmed that the fix is not expected to adversely impact load
test results
should improve results!
- [X] Alerted the release DRI if additional load testing is needed
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Enhancements**
* CVE vulnerability tracking is now scoped to a curated set of critical
vulnerabilities, improving the relevance of security impact data
displayed across your systems.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45247)
<!-- 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#42405
Unreleased bug fix.
# Checklist for submitter
## 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**
* Android MDM profile detail messages (e.g., certificate status) are
preserved when adding additional profiles or marking profiles pending,
preventing loss of important status information.
* **Tests**
* Added an integration test verifying ONC/certificate detail is retained
when additional Android MDM profiles are uploaded for the same team.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45299)
<!-- 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#44046
# Details
Updates GitOps to ensure that team labels are created before any
consumers (e.g. profiles, policies, software) that might use the labels
are applied. It does this by adding a new `afterTeamApply` callback
option to `ApplyGroup` that is called after team config is applied --
this is when team labels are now created, instead of after `ApplyGroup`
runs.
# 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
- [ ] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed GitOps deployments failing when a label and a profile that
references it are created in the same run.
* Ensures the correct apply ordering so dry-run/log output shows label
application before profile application.
* **Tests**
* Added a regression test validating label → profile apply ordering to
prevent future regressions.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45194)
<!-- 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#44801
Fixed remaining issue setting end user authentication in the UI.
The root cause was that the frontend was always sending
`enable_managed_local_account : false` even when macOS MDM was disabled.
Fix:
<img width="756" height="363" alt="image"
src="https://github.com/user-attachments/assets/5f88797d-c5c3-4c03-9048-c8ee2981c374"
/>
# Checklist for submitter
## Testing
- [x] Added/updated automated tests
- [x] 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
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Automatic syncing of "Lock end user info" when toggling End User
Authentication now only runs when Apple MDM is enabled and configured
(with an exception when EUA is being disabled to preserve behavior).
* Save payloads omit Apple-only fields unless Apple MDM is configured.
* **UI**
* "Lock end user info" checkbox shows an Apple MDM–gated tooltip and is
disabled when Apple MDM is not configured.
* **Tests**
* Added coverage for Apple MDM–gated behaviors and saved-payload
variations.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45212)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
**Related issue:** Resolves#45220 (one of many small PRs)
## Testing
- [x] QA'd all new/changed functionality manually. (Ran basic smoke
testing.)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Added a consolidated test utilities package to boot full test servers,
seed users, retrieve auth tokens, and provide common mocks for
integration/service tests.
* Updated integration and service tests to use the new helpers for more
consistent, reliable test setup and wiring.
* **Chores**
* Centralized and reorganized test configuration types and options, and
standardized test wiring for clearer, maintainable test setup.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45221)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Part of #38790. Stacked on top of #44935.
Closes#43968.
The two activate functions are the only `InstallApplication` command
builders, so fetching configuration and substituting `$FLEET_VAR_*`
inside them covers every enqueue path (manual, self-service, policy
auto-install, scheduled auto-update, setup experience, retry, admin
reinstall) by construction. Documents this invariant on each function.
Adds integration tests asserting an iOS install enqueues
`<key>Configuration>` with the host UUID substituted in, and macOS
installs omit the field.
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
Part of #38790. Stacked on top of #44934.
Closes#43967.
Adds `SubstituteFleetVarsInAppConfig` in `server/mdm/apple`, called from
`nanoEnqueueVPPInstall` and `activateNextInHouseAppInstallActivity`
right before the `InstallApplication` plist is built. Reuses
`profiles.ReplaceFleetVariableInXML` (XML-escapes substituted values)
and `profiles.ReplaceHostEndUserIDPVariables` for IDP fields.
Supports the full `FleetVarsSupportedInAppleAppConfig` allow-list:
`HOST_UUID`, `HOST_HARDWARE_SERIAL`, `HOST_PLATFORM`,
`HOST_END_USER_EMAIL_IDP`, `HOST_END_USER_IDP_USERNAME` / `_LOCAL_PART`
/ `_GROUPS` / `_DEPARTMENT` / `_FULLNAME`. Returns
`ErrUnresolvableAppConfigVar` when the host can't supply a referenced
variable (e.g. end-user IDP not enrolled) so the caller can fail the
install rather than send an empty value to the device.
Pulls `hardware_serial` onto the host SELECT so `HOST_HARDWARE_SERIAL`
is available without a second round-trip.
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
Part of #38790. Stacked on top of #44933.
Closes#43966.
The `InstallApplication` MDM command for VPP and in-house (`.ipa`) apps
was assembled inline in SQL via `CONCAT`, which can't carry
per-(adam_id, team_id, platform) configuration. Move the assembly into
Go: new `BuildInstallApplicationCommand` helper in `server/mdm/apple`,
plus refactored `nanoEnqueueVPPInstall` and
`activateNextInHouseAppInstallActivity` to SELECT pending tuples,
bulk-fetch configurations, build per-host plist bytes, and batch-INSERT
into `nano_commands`.
For iOS / iPadOS, the `Configuration` dict is inlined when stored
configuration is present. macOS VPP installs always drop the field
regardless of input. Empty / absent configuration omits the
`<key>Configuration>` entry — Apple treats this as "clear any managed
config for this app on next apply."
`$FLEET_VAR_*` tokens in the configuration are passed through
unsubstituted in this PR; per-host substitution comes in #43967.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Enhanced Apple app installation handling with improved command
generation and configuration management for VPP and in-house app
deployments.
* **Tests**
* Added comprehensive test coverage for app installation command
generation.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44934)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
Part of #38790. Stacked on top of #44932.
Closes#43969.
`fleetctl gitops` reads `configuration.path` for iOS / iPadOS VPP apps
and in-house `.ipa` apps, validates the file via
`fleet.ValidateAppleAppConfiguration`, and stores the bytes through the
service layer.
`fleetctl generate-gitops` emits `configuration.path` for apps that have
a stored configuration, writing the XML to
`lib/<team>/software/<app-slug>-config.xml`. Apps without configuration
omit the block entirely.
Round-trip integration tests
(`cmd/fleetctl/integrationtest/gitops/software_test.go`) cover apply +
generate for both VPP and in-house, plus a negative case for invalid
configuration content.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added managed app configuration support for enterprise software
deployments through GitOps
* Configurations now support platform-specific formats: XML for
iOS/iPadOS, JSON for Android
* Configuration validation ensures compatibility and prevents invalid
variable references
* **Tests**
* Added integration tests for configuration handling in software
deployment workflows
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44933)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
Part of #38790. Stacked on top of #44931.
Closes#43965.
Wires managed configuration through the existing REST endpoints (`POST
/api/.../app_store_apps`, `PATCH
/api/.../software/titles/:id/app_store_app`, in-house `.ipa` upload /
update). Validation runs at the service layer for iOS / iPadOS; macOS
VPP installs silently drop the field. Wire format: a JSON-encoded string
of the XML plist on POST/PATCH and on GET single-title responses (not
base64).
Includes `server/service/integration_apple_vpp_config_test.go` with
end-to-end coverage: add / update with valid plist, allowed
`$FLEET_VAR_HOST_UUID`, omit-field-no-change, **`configuration: null` →
row deleted** (regression test for the clear-on-null fix in #43964),
malformed XML → 422, disallowed Fleet variable → 422, and macOS
silent-drop pre- / post-validation.
Also drops a stray `fmt.Println("auth")` in `SoftwareTitleByID`'s
authorization-failure branch.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added support for managed app configuration on iOS/iPadOS devices
through VPP and in-house installers.
* Configuration now validates plist format and detects disallowed Fleet
variables.
* **Bug Fixes**
* macOS apps now correctly ignore configuration settings as expected.
* **Tests**
* Added comprehensive integration tests for Apple VPP and in-house
installer configuration workflows.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44932)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
Co-authored-by: Jonathan Katz <44128041+jkatz01@users.noreply.github.com>
Part of #38790. Stacked on top of #44930.
Closes#43964.
Adds VPP and in-house datastore methods (`GetVPPAppConfiguration`,
`BulkGetVPPAppConfigurations`, `DeleteVPPAppConfiguration`,
`HasVPPAppConfigurationChanged`, plus in-house equivalents) keyed on the
merged `vpp_app_configurations` and `in_house_app_configurations`
tables. Wires them into `InsertVPPAppWithTeam`, `SaveInHouseAppUpdates`,
and the team / app removal paths.
Two follow-up bug fixes folded into this branch:
1. iPadOS in-house apps received no configuration: a single `.ipa`
upload creates two `in_house_apps` rows but config was only stored
against the iOS row's id, so iPadOS lookups returned NotFound. Now
writes to both sibling rows on insert and propagates updates / clears
via `installerIDsForInHouseAppSibling`.
2. Single-app PATCH with `"configuration": null` was inserting empty
bytes instead of deleting; aligned the iOS/iPadOS branch in
`InsertVPPAppWithTeam` with the batch path's `len > 0` upsert / `len ==
0` delete semantics.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added support for managing in-house app configurations including
storage, updates, and deletion.
* Expanded VPP app configuration support for iOS/iPadOS platforms with
configuration storage and change detection.
* **Refactor**
* Updated Android app configuration handling to improve internal data
consistency and streamline configuration management workflows across all
app types.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44931)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#43776
ESP (Enrollment Status Page) related fixes after manual QA.
Note that we have an email thread with Microsoft regarding improving the
UX of the ESP page/flow.
# Checklist for submitter
## 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**
* Improved Windows MDM Enrollment Status Page (ESP) handling so
user-scoped provisioning nodes are created reliably during the
enrollment hold phase, reducing transient provisioning failures.
* On timeout paths, emit a canceled setup-experience activity for the
first pending/running setup item to ensure clearer provisioning state.
* **Tests**
* Strengthened tests to assert precise ESP command outputs and require
completion signals at both device and user scopes.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44740)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Konstantin Sykulev <konst@sykulev.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
**Related issue:** Resolves#44798
# 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
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance Improvements**
* Optimized Windows MDM profile removal operations for improved
performance when managing device profiles.
* **Bug Fixes**
* Enhanced Windows profile handling during host team transfers to ensure
correct profiles are properly installed and removed based on team
configuration.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45203)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolves#42930
- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
Ready for review, pending
[this](https://fleetdm.slack.com/archives/C084F4MKYSJ/p1778593457182719)
UX question.
## 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**
* Host activity details are now recorded and displayed for every attempt
— including queued and pending retries — of script executions and
software installations triggered by policy automations.
* **Tests**
* Integration tests updated to assert activity creation for each failed
attempt and retry flows.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45233)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Closes#45024
## Summary
- Fixed the MDM SSO callback handler returning a `"missing profile:
missing profile"` error when an Android device enrolls via SSO (OTA
enrollment) on a Fleet instance that does **not** have Apple MDM
configured.
- Refactored all MDM SSO initiator magic strings (`"ota_enroll"`,
`"setup_experience"`, `"account_driven_enroll"`) into named constants
(`fleet.SSOInitiatorOTAEnroll`, etc.) to prevent typos and missed cases
— which is the class of bug that caused this issue.
## Code walkthrough
### The bug
The bug is in `ee/server/service/mdm.go` in
`mdmSSOHandleCallbackAuth()`.
**The flow:**
1. Android enrollment hits `/enroll?enroll_secret=xxx` → frontend calls
`InitiateMDMSSO` with initiator `"ota_enroll"`
(`server/service/frontend.go:248`)
2. User authenticates at the SAML IdP
3. The SSO callback arrives at `MDMSSOCallback` → calls
`mdmSSOHandleCallbackAuth`
4. After successful SAML auth, the function checks early-exit
conditions:
- Line 1133: account-driven enrollment (`originalURL ==
appleMDMAccountDrivenEnrollmentUrl`) → **no match** for OTA
- Line 1139: `Initiator != "setup_experience"` → **true** for
`"ota_enroll"` → enters the block
5. Line 1140: calls `getAutomaticEnrollmentProfile()` → returns `nil`
because **no Apple MDM is configured**
6. Line 1144–1146: `depProf == nil` → **returns `"missing profile"`
error**
Note that `MDMSSOCallback` (the caller) already has a guard at line 931
that correctly skips the Apple MDM verification for `/enroll?` paths:
```go
if !strings.HasPrefix(originalURL, "/enroll?") && ssoRequestData.Initiator != "setup_experience" {
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil { ... }
}
```
But `mdmSSOHandleCallbackAuth` was missing the equivalent guard — it
unconditionally tried to fetch the Apple DEP profile for any
non-`setup_experience` initiator.
### The fix
Adds an early return for OTA enrollments (where `originalURL` starts
with `/enroll?`), matching the existing pattern for account-driven
enrollments right above it. OTA enrollments don't use the Apple DEP
profile token.
### The refactor
Replaced all raw initiator string literals across the backend with named
constants defined in `server/fleet/app.go`:
| Constant | Value | Used by |
|---|---|---|
| `fleet.SSOInitiatorOTAEnroll` | `"ota_enroll"` | `/enroll` page
(Android, BYOD iPhone/iPad) |
| `fleet.SSOInitiatorSetupExperience` | `"setup_experience"` | Orbit
agent (macOS Setup Assistant) |
| `fleet.SSOInitiatorAccountDrivenEnroll` | `"account_driven_enroll"` |
Apple account-driven MDM enrollment |
Constants are in `server/fleet/` (not `server/sso/`) so orbit can import
them without pulling in Redis dependencies.
**Files changed:**
- `ee/server/service/mdm.go` — 6 string replacements (switch cases +
comparisons)
- `server/service/frontend.go` — 1 replacement
- `orbit/cmd/orbit/orbit.go` — 1 replacement
- `server/service/testing_client.go` — 1 replacement
- `server/service/integration_mdm_test.go` — 1 replacement
## Local reproduction
### Setup
1. Started dev server: `build/fleet serve --dev --dev_license`
2. Infrastructure: MySQL, Redis, SimpleSAML IdP via `docker compose up`
3. Created admin user and enroll secret
4. Configured MDM SSO (`entity_id: mdm.test.com`, SimpleSAML IdP at
`localhost:9080`)
5. Set `enable_end_user_authentication: true` directly in DB (API blocks
this without Apple MDM — matches customer state)
6. **Did NOT configure Apple MDM** — only SSO + EUA, simulating
Android-only instance
### Steps
1. `GET https://localhost:8080/enroll?enroll_secret=test_enroll_secret`
→ 303 redirect to SimpleSAML IdP
2. Completed SAML login programmatically (user: `sso_user`, pass:
`user123#`)
3. `POST https://localhost:8080/api/v1/fleet/mdm/sso/callback` with the
SAMLResponse
### Before fix
```
=== CALLBACK RESULT ===
Status: HTTP/2 303
Location: /mdm/sso/callback?error=true
=== SERVER LOGS ===
ts=2026-05-08T16:53:49Z level=error component=http method=POST
uri=/api/v1/fleet/mdm/sso/callback took=12.148708ms
err="missing profile: missing profile"
```
### After fix
```
=== CALLBACK RESULT ===
Status: HTTP/2 303
Location: /enroll?enroll_secret=test_enroll_secret&enrollment_reference=7c67326c-...&initiator=ota_enroll&profile_token=
=== SERVER LOGS ===
ts=2026-05-08T17:27:54Z level=info component=http method=POST
uri=/api/v1/fleet/mdm/sso/callback took=15.973ms
```
No errors. Successful redirect back to the enrollment page with the
enrollment reference.
## Integration test
Added `TestOTAEnrollSSOWithoutAppleDEPProfile` which:
1. Configures SSO and creates a team with IdP enabled
2. **Deletes all Apple DEP enrollment profiles** to simulate an
Android-only instance
3. Runs the full OTA enrollment SSO flow (GET `/enroll` → SAML IdP login
→ callback)
4. Verifies the callback redirects to `/enroll?...` with
`enrollment_reference` and `initiator=ota_enroll` (not `?error=true`)
Confirmed the test **fails without the fix** (`err="missing profile:
missing profile"`) and **passes with the fix**.
Also added a `LoginOTAEnrollSSOUser` test helper that drives the
complete OTA SSO flow starting from `GET /enroll` through SAML IdP login
to the callback, using a single cookie jar.
## Test plan
- [ ] Verify Android SSO enrollment works on an instance with **only**
Android MDM configured (no Apple MDM)
- [ ] Verify Apple DEP enrollment with SSO still works (the DEP profile
path is unchanged)
- [ ] Verify Apple OTA enrollment with SSO still works (also uses
`/enroll?` path)
- [ ] Verify account-driven enrollment with SSO still works (has its own
early return)
- [ ] Verify setup experience SSO still works (uses `Initiator ==
"setup_experience"`)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Resolved a regression where OTA enrollment via SSO could return a
"missing profile" error on Android when Apple MDM is not configured; OTA
SSO now redirects correctly to the enrollment flow.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45046)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Magnus Jensen <magnus@fleetdm.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#44456
# 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.
- [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] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Dry-run now performs Apple config profile payload scope conflict
validation and reports unknown Fleet variables for all profile types
before completing.
* **Tests**
* Added tests covering Apple config profile scope-conflict validation
and dry-run/batch profile workflows to ensure conflicts are detected in
both dry-run and live flows.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45139)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
**Related issue:** Resolves#42613
Dedupes errors that report HTTP 408 (request timeouts). As of now, I
believe this only fires for timeouts on the
**/api/v1/osquery/distributed/write** endpoint.
This is so that we have a unique error hash with an incrementing count,
instead of thousands of entries each with count: 1, which produces a
huge JSON payload when passed to
https://fleetdm.com/api/v1/webhooks/receive-usage-analytics for
processing.
Trade-off:
- Before: every occurrence got its own Redis entry so thousands of
near-identical examples coexisted.
- After: they collapse into one entry whose :json value still contains a
representative example, but we'd only keep the last IP+Port instead of
all of them.
# 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
Build a ~5 MB JSON body in a temporary file:
```bash
{ printf '{"node_key":"'; head -c 5000000 /dev/zero | tr '\0' 'x'; printf '"}'; } > /tmp/distwrite-body.json
```
Clear out redis:
```bash
docker exec fleet-redis-1 redis-cli FLUSHDB
```
Send a dummy request and throttle the upload at 100 KB/s → ~50s to send,
read timeout fires at 25s.
I sent this 3 times and got the "request body read error" error back
after each request.
```bash
curl -sk --limit-rate 100K -X POST -H 'Content-Type: application/json' --data-binary @/tmp/distwrite-body.json https://127.0.0.1:8080/api/v1/osquery/distributed/write
{
"error": "request body read error: i/o timeout",
"uuid": "95937f50-1008-4625-9423-bc19c7be6818"
}
```
Count the error keys containing "request body read error" as the value.
```bash
docker exec fleet-redis-1 sh -c 'for k in $(redis-cli --scan --pattern "error:*:json"); do v=$(redis-cli GET "$k"); echo "$v" | grep -q "request body read error" && echo "$k count=$(redis-cli GET "${k%:json}:count")"; done'\
error:{Cco_JmAdBVVVJI9k0XjNNUCmG0z1IKguMQD4VDaejfc=}:json count=3
```
Notice the single entry and count=3 (since I ran the dummy request 3
times).
Running this on main outputs three entries each with count=1.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Network error deduplication for request-timeout errors now normalizes
socket addresses, preventing the usage statistics cron from failing when
many similar network errors accumulate.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45142)
<!-- 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#44801
Note there is a related bug:
https://github.com/fleetdm/fleet/issues/45170
# 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`.
## Testing
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* End user authentication can now be enabled for Windows-only and
Linux-only fleets without requiring macOS MDM configuration.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45162)
<!-- 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#42886
# 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
- [X] gitops run with extra keys (besides `client_email` and
`private_key` in `api_key_json` fails on main, passes on this branch
- [X] gitops run with missing `client_email` or `private_key` in
`api_key_json` still fails gitops (including dry run)
- [X] gitops run with extra keys sibling to api_key_json still fails as
expected
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Corrected GitOps validation so Google Calendar API key JSON no longer
rejects valid nested keys; required-field validation for the integration
still enforced.
* **Tests**
* Added test coverage to ensure nested unknown keys are accepted while
sibling-level unknown fields are reported as validation errors.
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44556)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tim Lee <timlee@fleetdm.com>
Adds a linter to ensure we don't add new instances of `team` or `query`
in API params. This will be used incrementally, but this PR also adds
`nolint` directives to places that still have these terms, both to avoid
false-positives later and to help with full migration away from these
terms in in Fleet 5.
Example:
```
server/fleet/campaigns.go:51:16: json tag "team_id": uses deprecated "team"/"teams" — use "fleet"/"fleets" instead (apiparamcheck)
Team *uint `json:"team_id,omitempty"`
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a new static analyzer (apiparamcheck) to flag deprecated API
parameter names ("team/teams") and improper usages of "query/queries".
* **Chores**
* Integrated the new check into CI tooling and configuration.
* Added analyzer tests and plugin registration.
* Applied targeted lint-suppression annotations across code and tests
where legacy parameter names must remain.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
**Related issue:** Resolves
https://github.com/fleetdm/fleet/issues/44949.
- [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
For unreleased bug fixes in a release candidate, one of:
- [X] Confirmed that the fix is not expected to adversely impact load
test results.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Policy retrieval now correctly enforces team authorization, preventing
unauthorized cross-team access and ensuring team policies are returned
properly.
* **New Features**
* UI uses a unified policy access path for viewing/editing policies,
improving consistency for inherited/team-scoped policies,
back-navigation, and fleet-name display (All fleets / No team).
* **Tests**
* Added unit and integration tests covering cross-team access rules and
that policy automation fields are populated when policies are returned.
<!-- 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#44286
Unset `patch_software_title_id` rather than deleting the policy in
`BatchSetSoftwareInstallers`, so the orphaned policy gets picked up by
the `policiesToDelete` loop in `server/service/client.go:3121`. As a
result, the `deleted_policy` activity is now created properly, and
gitops dry/real runs also report the deletion:
```
dry run:
[-] would've deleted policy macOS - 010 Editor up to date
[-] would've deleted 1 policy
real run:
[-] deleting policy macOS - 010 Editor up to date
[-] deleting 1 policy
[-] deleted 1 policy
```
# 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.
- [ ] 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.
- [ ] Timeouts are implemented and retries are limited to avoid infinite
loops
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes
## Testing
- [x] Added/updated automated tests
- [ ] 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
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed missing deletion activity logs when patch policies are removed
via GitOps so policy deletion events are now recorded.
* **Behavior Changes**
* Batch-updating installers now retains obsolete patch policies but
clears their patch installer reference instead of deleting the policy.
* **Tests**
* Added integration coverage to verify deletion activities are emitted
and installer batch behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolves#38437
The list activities endpoint applied an implicit `created_at <= now` cap
only when `start_created_at` was set, leaving the upper bound unbounded
in every other case, this was changed so that we now apply that cap
unconditionally and override only when the caller passes an explicit
`end_created_at` (as peer the REST docs).
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#45042
# Details
On Dogfood, we have v4.85.0 server running but we run our GitOps with
the currently published fleetctl (4.84). This mismatch caused us to
disable (and therefore wipe out data for) both of our historical chart
datasets. This PR patches the "update app config" code so that when in
"overwrite mode" (i.e. GitOps), it checks for empty `historical_data`
keys in the incoming JSON and replaces them with the default values
(currently `true`, i.e. "collect the data"). Tested manually (see
testing below).
# 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.
n/a, unreleased
## 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
- [X] reproduced issue on both current fleet v4.85 and main branch
servers, using fleetctl v4.84
- [X] on this branch, ran fleetctl v4.84 w/out `historical_data` in
gitops and verified that charts were enabled.
- [X] on branch applied to 4.85, ran fleetctl v4.84 w/out
`historical_data` in gitops and verified that charts were enabled.
- [X] disabled one chart in the UI, and verified that updating unrelated
app config in the UI did not affect that config (PATCH still works)
For unreleased bug fixes in a release candidate, one of:
- [X] Confirmed that the fix is not expected to adversely impact load
test results
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Fixed GitOps configuration handling for historical data settings to
properly apply default values when fields are omitted by clients. This
ensures that previous configuration settings are preserved correctly in
overwrite mode, preventing incorrect defaults from being inadvertently
persisted when managing configurations with older clients.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Added an optional HTTP-level pre-auth middleware (enabled using the
FLEET_OSQUERY_ALLOW_BODY_AUTH_FALLBACK server config) that validates
incoming osquery requests based on `Authorization: NodeKey <node_key>`
header.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#44980
# 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.
- [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
- [ ] QA'd all new/changed functionality manually (Not, outside of tests
due to exercising replica lag is difficult)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Improved reliability of device profile assignment by ensuring all
devices receive profiles consistently, even when replica lag affects
device synchronization from Device Enrollment Program services.
* **Tests**
* Added test coverage validating device profile assignment behavior
under replica lag scenarios.
<!-- 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#44376
I opted for an in-memory cache here, as it's not a critical cache piece,
we are fine with the cache being different times on different containers
(just means some might rotate to the correct cert faster than 5
minutes).
It's also a small piece of work, rather than pulling in redis etc.
Verified that it now logs, if the cert is stale after a 5 minute
in-memory cache.
```
ts=2026-05-07T11:30:08Z level=info msg="push certificate is stale after re-checking" topic=com.apple.mgmt.External.34c4a9b0-6501-4ce6-afc6-32eac6420ee7 staleToken="\x90C\xe4K\xc6a\x97\xb5?\x1b\x9a\x04'\xe7b\x8d" newHash=".fP\xc7O7\xab\xab\x9d\x92\xd5#\xe4u\xe0\xf6"
ts=2026-05-07T11:30:08Z level=info component=apple-mdm-push msg="retrieved push cert" topic=com.apple.mgmt.External.34c4a9b0-6501-4ce6-afc6-32eac6420ee7
```
# 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.
- [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] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* APNs push certificates now refresh in-memory when rotated; staleness
is detected using certificate checksums with a short grace window.
* **Tests**
* Added tests for certificate retrieval, staleness detection/refresh
behavior, and push-cert storage error handling.
* **Documentation**
* Updated docs to describe the APNs push-certificate refresh and
staleness behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Closes#44950
## Local reproduction
Reproduced locally using a MySQL integration test against the local test
database. The test simulates the exact GitOps scenario from the issue:
1. Create a label and associate it with an MDM profile
2. Delete the label (FK `ON DELETE SET NULL` sets `label_id = NULL`)
3. Create a new label with the **same name** (simulates moving from
global to fleet scope)
4. Call `batchSetProfileLabelAssociationsDB` with the profile
referencing the new label
**Before fix** (code from `main`, unfixed):
```
$ MYSQL_TEST=1 go test -run "TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated" -v -count=1 ./server/datastore/mysql/...
=== RUN TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin
Error: selecting existing profile labels: sql: Scan error on column index 1, name "label_id": converting NULL to uint is unsupported
=== RUN TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows
Error: selecting existing profile labels: sql: Scan error on column index 1, name "label_id": converting NULL to uint is unsupported
--- FAIL: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin (0.02s)
--- FAIL: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows (0.02s)
FAIL
```
**After fix:**
```
$ MYSQL_TEST=1 go test -run "TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated" -v -count=1 ./server/datastore/mysql/...
=== RUN TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows
=== RUN TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin
--- PASS: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows (0.03s)
--- PASS: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin (0.03s)
PASS
ok github.com/fleetdm/fleet/v4/server/datastore/mysql 2.761s
```
## Code changes
When a label is deleted, MySQL's `ON DELETE SET NULL` foreign key
constraint automatically sets `label_id = NULL` in the profile-label
association row. The Go code then crashes trying to scan that NULL into
a `uint` field.
- **`server/datastore/mysql/mdm.go`** — Added `COALESCE(label_id, 0)` to
the SELECT in `batchSetProfileLabelAssociationsDB`, so that NULL
`label_id` values are returned as 0 instead of causing a scan error when
Go tries to read NULL into a `uint`.
- **`server/datastore/mysql/apple_mdm.go`** — Same `COALESCE(label_id,
0)` fix in `batchSetDeclarationLabelAssociationsDB`. Also added `OR
label_id IS NULL` to the DELETE statement to clean up broken rows,
matching the profile labels behavior from #42637.
Other queries in the same codebase (e.g.,
`listProfileLabelsForProfiles`) already use `COALESCE(label_id, 0)` —
these two were missed.
## Testing
- `same_label_name_recreated_after_deletion_{darwin,windows}` —
reproduces the exact bug: associates a profile with a label, deletes the
label (NULL label_id), creates a new label with the same name, and
verifies `batchSetProfileLabelAssociationsDB` succeeds, the broken row
is cleaned up, and the correct label association exists
- Full MDM test suite passes: `MYSQL_TEST=1 go test -run "TestMDM"
./server/datastore/mysql/...` (76s)
- `make lint-go-incremental` passes
**Related issue:** Resolves#44391
# 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
I tested this locally
number of ubuntu hosts: 6,252
average software per host: 2,302
distinct software items: 61,213
host_software rows: 14.4M
generates software_cve rows 305,826
OS sub-versions: 25
The time before my optimization **10m53s** down to **4m26s** the
optimization.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Optimized OSV vulnerability scanning to aggregate work by OS version
and batch lookups, reducing redundant queries for faster scans.
* **Refactor**
* Restructured scanning flow to process OS versions in batched chunks
with clearer logging and early exits when no work is required.
* **Tests**
* Added tests for querying, batching, source filtering, deduplication,
and empty-input behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
**Related issue:** Resolves#44391
# 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] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Added release notes documenting vulnerability scanning performance
improvements
* **Refactor**
* Optimized vulnerability scanning performance through enhanced CVE
product matching efficiency
<!-- 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#44958
# Details
Fixes two issues on the checkerboard:
1. Ensures that the chart shows data going back 30 calendar days (not
720 hours) if it has it
2. Leaves `0` values out of the chart color band calculations in
"relative" color mode
# 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.
n/a, unreleased
## Testing
- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
**Before**
Colors clustered in top 3 levels, empty boxes in first column:
<img width="705" height="416" alt="image"
src="https://github.com/user-attachments/assets/b867a1a9-4c52-4b96-92fd-04e7848c6295"
/>
**After**
Colors spread over all levels, no empty boxes in first column:
<img width="707" height="412" alt="image"
src="https://github.com/user-attachments/assets/c67e11c5-6dd8-4e66-9c32-9d9213ccb24f"
/>
For unreleased bug fixes in a release candidate, one of:
- [X] Confirmed that the fix is not expected to adversely impact load
test results
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Charts request an extra day to ensure full calendar-day coverage
across timezones.
* Checkerboard visualization excludes empty/no-data slots from relative
color scaling so color ramps reflect non-zero data.
* Calendar view trims leading partial days so the displayed window
matches the selected range.
* **New Features**
* Chart date-range selection expanded to support any value from 1–31
days.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolves#34104
When saving SMTP settings with SSL/TLS off, STARTTLS on, and SSL cert
verification on, the test-email send produced an opaque Go cert error
that gave users no actionable hint. The two TLS-related toggles also
live on different settings cards with no cross-reference, which made the
conflict hard to spot before hitting Save.