Commit Graph
4652 Commits
Author SHA1 Message Date
Sharon Katzandcopilot-swe-agent[bot] 5c127e5fe4 Fix software ingestion lock convoys and unbatched deletes (#49894)
**Related issue:** Resolves #49805, Resolves #48719

# 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

## 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

---

## Context

A customer (~2,500 hosts, v4.89.1) had their DB writer slammed with
`DELETE FROM host_software_installed_paths` statements carrying 30,000+
IDs each. These never completed, required repeated manual intervention,
and the table grew from 14.5M to 14.8M rows in 2 days. This is #49805.

While investigating, Victor linked #48719, a related `software_titles`
INSERT lock convoy issue seen in load tests. Both are in the same
software ingestion code path (`server/datastore/mysql/software.go`), so
this PR fixes both.

## Root cause

### #49805: Unbatched DELETEs on `host_software_installed_paths`

When a host's software changes, Fleet computes a delta and deletes stale
rows from `host_software_installed_paths`. The function
`deleteHostSoftwareInstalledPaths()` issued a **single** `DELETE FROM
host_software_installed_paths WHERE id IN (?)` with all IDs expanded by
`sqlx.In()`. With 30,000+ IDs and 14.8M rows in the table, these massive
statements held row locks for minutes, timed out, and never completed.
On the next agent check-in, the same (or larger) DELETE was retried,
creating a feedback loop where the table grew unboundedly.

Notably, the INSERT function for the same table
(`insertHostSoftwareInstalledPaths`) already batched at 500 rows. The
DELETE simply lacked the same treatment.

### #48719: INSERT IGNORE lock convoys on `software_titles` (related)

When a host reports software that Fleet hasn't seen before,
`preInsertSoftwareInventory()` runs `INSERT IGNORE INTO software_titles
(...)` inside a `withRetryTxx` transaction. For homogeneous fleets (many
hosts sharing the same software catalog, typical for imaged corporate
Windows machines), hundreds of concurrent goroutines try to INSERT
IGNORE the same title rows simultaneously.

Even though `INSERT IGNORE` is a no-op when the row already exists,
InnoDB still acquires row/gap locks on the unique index for the duration
of the enclosing transaction. With many goroutines holding or waiting on
the same index locks, the DB enters a "lock convoy" where sessions
serialize on locks they don't actually need. In load tests (40 Fleet
instances, 100K hosts, 141 identical Windows software items), this
produced 690 average active sessions on the writer and 85s fleet-wide
p99.

The existing read-first check
(`getIncomingSoftwareChecksumsToExistingTitles`) prevents the convoy at
steady state. But on cold start (empty `software_titles`, e.g. after
cleanup purges orphaned titles), the check finds nothing and all
goroutines race to INSERT the same titles.

## How I reproduced it

Started MySQL via `docker compose up -d mysql_test`, created a git
worktree.

### #49805

`TestHostSoftwareInstalledPathsDeleteExplosion`: Created a host with 500
software items and installed paths, then replaced all software with an
entirely new set. This triggers `deleteHostSoftwareInstalledPaths()`
with all 500 old IDs in a single unbatched DELETE statement. At 500 IDs
the local test completes quickly, but the structure confirms the
problem: at 30K+ IDs on production Aurora with 14M rows, these never
finish.

### #48719

`TestSoftwareTitlesInsertIgnoreLockConvoy`: Created 50 hosts, each
reporting 100 identical software items (simulating a homogeneous fleet).
Used a barrier to release all 50 goroutines simultaneously, then
measured two phases:

1. **Cold start** (empty `software_titles`): All 50 hosts concurrently
call `ds.UpdateHostSoftware()`.
2. **Steady state** (titles exist): Same 50 hosts re-ingest.

**Before fix:**
| Metric | Cold start | Steady state |
|--------|-----------|-------------|
| Wall time | 3.0s | 38ms |
| Avg per-host | 1,981ms | 29ms |
| **Convoy factor** | **79x** | |

The 79x slowdown confirms the lock convoy.

## How I fixed it

### #49805: Batch the DELETE at 500

Changed `deleteHostSoftwareInstalledPaths()` from a single `DELETE ...
WHERE id IN (all IDs)` to a loop that processes 500 IDs per batch,
matching the existing INSERT batching pattern in the same file.

### #48719: Three-layer defense against lock convoys

**Layer 1 - Move title INSERT IGNORE outside the transaction.**
Previously, `INSERT IGNORE INTO software_titles` ran inside
`withRetryTxx`, so locks were held for the full transaction duration.
Now each title INSERT is executed via `ds.writer(ctx).ExecContext()`
outside any transaction, auto-committing independently and holding locks
for microseconds.

**Layer 2 - singleflight per title key.** Added a `singleflight.Group`
on the `Datastore` struct. For each title, only one goroutine actually
executes the INSERT; concurrent goroutines wait and share the result.

**Layer 3 - In-process cache (`sync.Map`).** After a title is inserted,
its key is stored in `knownSoftwareTitleKeys`. Subsequent ingestions
check the cache first and skip the INSERT entirely.
`CleanupSoftwareTitles` clears the cache when it deletes orphaned
titles.

The three layers work together: the cache handles the common case (title
already known), singleflight handles the cold-start race (only one
INSERT per title), and auto-commit ensures even the winning INSERT holds
locks for microseconds.

## How I tested that it works

### New reproduction tests

- `TestSoftwareTitlesInsertIgnoreLockConvoy`: 50 concurrent hosts, 100
identical software items. Measures cold-start convoy factor and verifies
all 100 titles are created.
- `TestHostSoftwareInstalledPathsDeleteExplosion`: Full software
replacement path with 500 items per host, including concurrent hosts.

### Existing test suite

Ran all existing software tests including:
- `UpdateHostSoftware`, `UpdateHostSoftwareDeadlock`,
`PreInsertSoftwareInventory`
- `SoftwareTitleUpgradeCodeDriftMatch`,
`UpdateHostSoftwareSameBundleIDDifferentNames`
- `CleanupSoftwareTitles` (validates cache invalidation works correctly)
- `SaveHost`, `SyncHostsSoftware`, and ~80 other subtests

All pass.

### After-fix measurements

| Metric | Before fix | After fix |
|--------|-----------|-----------|
| Cold-start wall (50 hosts) | ~3.0s | ~1.4s |
| Cold-start avg per-host | ~1,981ms | ~594ms |
| Steady-state wall | ~38ms | ~7ms |
| Titles created correctly | 100/100 | 100/100 |

The remaining cold-start time is from other pipeline operations (`INSERT
IGNORE INTO software`, host_software linking), not from
`software_titles`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Performance Improvements**
- Improved software inventory ingestion under large, concurrent
workloads, including more efficient handling of repeated software-title
inserts.
- Reduced lock contention when many devices report the same titles at
the same time.
- Batched deletions of installed software-path records to speed up large
updates.

- **Bug Fixes**
- Ensured deterministic, collation-safe software-title deduplication to
prevent incorrect or stale title mapping.
- Strengthened orphan cleanup behavior so caches are cleared when orphan
titles are removed.

- **Tests**
- Added stress/regression tests for software-title insert contention and
large installed-path delete workloads.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-24 15:46:14 -04:00
RachelElysia 09fea47ce4 Fleet UI: Handle long fleet names across the Fleets UI (#49216)
## Issue
Closes #47290

Also implements the "Cap free-text `maxLength` to the backend column
length" pattern established in [#49041 (patterns.md
thread)](https://github.com/fleetdm/fleet/pull/49041/files#r3572648691).

## Description
Fleet name inputs had no `maxLength` cap and no service-layer length
check, so a name >255 chars failed with a raw MySQL `Data too long`
error, and several UI surfaces didn't handle long names gracefully. This
PR fixes all four manifestations called out in the bug, plus a related
label-overflow case on the host details page, and hardens adjacent name
inputs across the app.

**Frontend fixes for #47290:**
- Create/Rename fleet name inputs now cap at 255 characters (matches
`teams.name varchar(255)`).
- Fleets table Name column uses `LinkCell` with `tooltipTruncate` +
`className="w400"` so long names truncate with an ellipsis and full-name
tooltip instead of overflowing across the Hosts/Users columns.
- Fleet-detail page header (`.team-details__team-header`): h1 gets
`overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`,
`__team-details` gets `min-width: 0; flex: 1`, and `.action-buttons`
gets `flex-shrink: 0` + `white-space: nowrap` on buttons so *Manage
enroll secrets / Rename / Delete* no longer wrap to a second line when
the fleet name is long.
- Manage enroll secrets modal body — `__description` gets
`overflow-wrap: anywhere; min-width: 0` so a long `<b>{fleet name}</b>`
wraps within the modal instead of spilling out the right edge.

**Backend fixes for #47290:**
- New `fleet.MaxTeamNameLength = 255` constant.
- `NewTeam`, `ModifyTeam`, and `ApplyTeamSpecs` now return
`fleet.NewInvalidArgumentError("name", "may not exceed 255 characters")`
instead of surfacing a raw `Data too long` MySQL error. Covers UI, API,
and GitOps entry points.

**Broader consistency pass (per [#49041
thread](https://github.com/fleetdm/fleet/pull/49041/files#r3572648691)):**
- New shared `MAX_ENTITY_CHAR_LENGTH = 255` constant in
`frontend/utilities/constants.tsx`.
- Refactored 8 existing files that had ad-hoc `NAME_MAX_LENGTH = 255` /
`MAX_LABEL_NAME_LENGTH = 255` locals to use it.
- Slotted it into 16 additional `InputField` name/description inputs
that were missing a cap (API user, custom variable, certificate, label
name + description, pack name + description, and all 5 CA forms —
CustomEST, CustomSCEP, Smallstep, Digicert, Hydrant).
- Pruned dead FE length validators that can no longer fire now that the
DOM cap enforces the limit (certificate modal, custom variable modal,
both label helpers, both category modals). Unusual/shorter caps (e.g.
`varchar(64)`, custom business rules) still keep their inline validators
— silent truncation is only appropriate for the common 255-char norm.

**Bonus:** fixed the long-label overflow on the host details Labels card
by capping the pill button `max-width` at 300px.

## Screenrecording



https://github.com/user-attachments/assets/b917b72e-7437-4d0c-a1a1-c49b4b1c28ba



https://github.com/user-attachments/assets/3a3efbb2-09d8-4f47-9fd4-f158b3453b9e



https://github.com/user-attachments/assets/73e5e022-dc93-4381-82b3-be9549d050e6

Latest - max width 300px long label:

<img width="1106" height="262" alt="Screenshot 2026-07-23 at 11 29
24 AM"
src="https://github.com/user-attachments/assets/741fddbd-f78d-4578-a025-bddf64a81c25"
/>


## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

Test coverage:
- `CreateFleetModal.tests.tsx`, `RenameFleetModal.tests.tsx` — new case
per file asserting the name input's `maxLength === 255`.
- `AddCertificateModal.tests.tsx`, `Variables.tests.tsx` — the existing
"shows too-long error when pasting 256 chars" tests are now unreachable
via the DOM cap; converted to `maxLength === 255` assertions.
- `ee/server/service/teams_test.go` — `TestNewTeamNameValidation`,
`TestModifyTeamNameValidation`, and `TestApplyTeamSpecsNameValidation`
each get two new cases (accepts at the limit, rejects one over with the
expected error message).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Limited fleet, team, and other user-entered names and descriptions to
255 characters.
* Replaced database errors for oversized names with clear validation
messages.
* Prevented long fleet and label names from overflowing tables, headers,
modals, and host details.
  * Improved modal and dropdown layouts for long text.
* **Tests**
* Added coverage for character limits, boundary values, and multibyte
names.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-24 11:22:06 -04:00
Juan Fernandez aeb56916ee Block host enrollment with empty enroll secrets
VerifyEnrollSecret matched by exact string, so an empty enroll_secret
matched any stored empty secret and issued a valid node key. Guard the
shared chokepoint: reject empty/whitespace secrets before matching,
closing all enrollment paths (osquery, Orbit, Apple MDM, Android). Add a
migration to delete pre-existing empty secrets.
2026-07-24 10:29:46 -04:00
RachelElysia 3e3097cf49 Fleet UI: Drop I-beam cursor on non-underlined tooltips (#49859) 2026-07-24 07:01:48 -07:00
NicoandCopilot Autofix powered by AI 6bb0b1ea52 Stop leaking cross-team software title names via the hosts endpoint (#49638)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves: N/A

# 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Corrected `software_title_id` filtering for hosts so software title
details are strictly scoped to the current team and never backfill
mismatched or out-of-scope data.
* Removed unintended debug output and ensured software title details
remain unset when the title isn’t accessible.
* **Tests**
* Added an enterprise integration test verifying cross-team
`software_title_id` behavior.
* Updated existing integration expectations for team-scoped visibility.
* Added datastore coverage for team-scoped software title name lookup
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-24 10:39:13 -03:00
Rajendra Kadam aa572dcca4 Show ABM organization name in edit-fleets success toast (#49877)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48914

## Description

The success toast shown after editing fleet assignments for an Apple
Business Manager (ABM) organization read:

> Successfully updated fleets for AB token.

The trailing "AB token" made the message unclear. It now names the
organization instead, matching the modal's title:

> Successfully updated fleets for `<org name>`.

The organization name (`token.org_name`) was already available in the
component (it's used as the modal title), so this is a copy-only change
with no new data plumbing.

Note: the issue's expected behavior left the exact wording to Product
("TODO — Product to decide"). This implements Product's written
suggestion (`Successfully updated fleets for {org name}`) so the awkward
wording isn't blocking; the string is trivial to adjust if
Product/design prefer different phrasing in review.

## Testing

- Manually QA'd in the UI: with a configured ABM organization, edited a
fleet assignment and confirmed the toast now shows the org name.
- Existing unit tests for the modal's helpers (`getOptions`,
`getSelectedTeamIds`) still pass; no test asserts the toast string.
- `eslint` and `prettier` pass on the changed file (added
`token.org_name` to the `useCallback` dependency array to satisfy
`react-hooks/exhaustive-deps`).

# Checklist for submitter

- [x] Changes file added for user-visible changes in `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] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated the success notification shown after fleet teams are saved to
include the associated organization name.
* Ensured the notification always reflects the currently selected
organization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-24 13:30:57 +05:30
Rajendra Kadam b22a046a96 Fix undeletable Apple DDM declarations when allowed types change (#49810)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #47535

## Description

`DeleteMDMAppleDeclaration` re-ran the upload-time validator
(`ValidateUserProvided`) on the delete path. That validator enforces
*upload-admission* rules — forbidden declaration types
(`ForbiddenDeclTypes`) and the `AllowAllDeclarations` config flag — so
any declaration that was accepted at upload time became **undeletable**
through the API once the accepted set later shrank. Two ways this
happens in practice:

- A server config flag that had been enabled at upload time is later
disabled (the original customer report, prod 4.86.1).
- A declaration type is added to `ForbiddenDeclTypes` in a later
release, after declarations of that type were already uploaded.

In both cases the UI showed "Couldn't delete. Please try again." and the
API returned `400` with an upload-validation message on a *delete*
request.

Whether a declaration is Fleet-managed (and therefore protected from
deletion through this endpoint) is already determined by the Fleet
reserved-name check that runs immediately above the offending block.
This PR removes the upload-time validation from the delete path and
relies solely on that reserved-name check, so:

- A user can delete any declaration they previously uploaded, regardless
of whether the current validator config would still accept it on upload.
- Fleet-managed declarations (reserved names) remain protected from
deletion.

The `AllowAllDeclarations` flag and `ValidateUserProvided` are unchanged
on the **add/upload** path — admission control still happens where it
belongs.

## Testing

Extended `TestMDMConfigProfileCRUD` (replacing the pre-existing `//
TODO: Add tests for create/delete forbidden declaration types?`) with
two cases:

- A declaration whose type is in `ForbiddenDeclTypes` can be deleted
(regression guard — fails before this change, passes after).
- A declaration with a Fleet-reserved name remains protected from
deletion (guards the reserved-name check that is now the sole
Fleet-managed gate — a boundary that was previously untested).

Manually verified end-to-end in the UI: reproduced the stuck declaration
(upload a forbidden type with `FLEET_MDM_ALLOW_ALL_DECLARATIONS=true`,
restart without the flag), confirmed the pre-fix `400`, then confirmed
deletion succeeds after the fix with the flag off.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `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] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved Apple MDM declaration deletion to avoid re-running
upload-time validation during the delete flow.
* Declaration deletion checks now rely on Fleet management status and
reserved naming, preserving protection for Fleet-managed declarations.
* Added/adjusted deletion behavior for restricted and Fleet-reserved
declaration types.
* **Tests**
* Added regression coverage for Apple declaration profile deletion via
the configuration profile delete endpoint, including strict-mode
scenarios and cleanup behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-24 11:10:38 +05:30
RachelElysia 9cfd907be6 Fleet UI: Hide empty host summary card on Free-tier Android hosts (#49848) 2026-07-23 12:54:18 -07:00
Victor Lyuboslavsky e42dc7accf Improved the performance of the configuration profiles status summary (#48873)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48340 

Windows only. The fix is to use a rollup status table instead of
recalculating the host profile summary on demand.

Verified the fix in load test with 100k Windows MDM hosts. Note that
this does not improve the host details page filtered by OS settings,
which will be handled by the follow up
https://github.com/fleetdm/fleet/issues/48996

# 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] Timeouts are implemented and retries are limited to avoid infinite
loops

## 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

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved Windows fleet configuration profile status summaries to avoid
timeouts on large fleets.
* Kept per-host Windows profile statuses accurate after profile updates,
resends, certificate changes, cleanup, unenrollment, and host deletion.
* Added automatic reconciliation to correct stale or orphaned status
data.
* **Data Integrity**
* Improved Windows profile status reporting, including profile and
BitLocker summaries, for more reliable results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-23 11:59:37 -05:00
Carlo 83f3f4b560 Add clear error for Firefox / Firefox ESR conflict (#49714)
**Related issue:** Resolves #49682

Mozilla Firefox and Firefox ESR are distinct Fleet-maintained apps that
share the macOS bundle identifier `org.mozilla.firefox`, so they resolve
to one software title. Adding both to a fleet previously gave a generic
conflict error (or no error at all). This adds a clear message — "Only
one of Mozilla Firefox or Mozilla Firefox ESR can be added to the same
fleet." — on both the single-add and GitOps/batch paths. The check is
general (any two FMAs sharing a bundle identifier), with the app names
filled in dynamically.

# 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
- [ ] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Prevented adding both Mozilla Firefox and Firefox ESR to the same
fleet when they share a bundle identifier.
- Updated the UI to show a specific conflict message explaining that
only one of the two can be added.
- Ensured existing workflows still work for adding new versions of the
already-selected app.
- **Tests**
- Added backend and frontend test coverage for the new conflict
detection and error-message formatting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-23 12:39:17 -04:00
Sharon Katz f492a6a41d Enforce API-only endpoint restrictions on chart routes (#49477)
# 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

## Summary

Enforced API-only endpoint restrictions on chart endpoints, matching the
pattern already used by the activity bounded context. Also added
`RouteTemplateRequestFunc` to chart route server options so the
middleware can read the matched mux route template from context.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

### Reproduction

Created an API-only user with a restrictive endpoint allow-list (only
`GET /api/v1/fleet/hosts`). Confirmed that:
- Allowed endpoint (`/api/latest/fleet/hosts`) returns 200
- Non-allowed cataloged endpoint (`/api/latest/fleet/users`) returns 403
- Chart endpoint (`/api/latest/fleet/charts/uptime`) returned 200 before
the fix (the bug)
- After the fix, chart endpoint correctly returns 403

### Unit test

Added a test case in `server/service/middleware/auth/api_only_test.go`
that verifies an API-only user with endpoint restrictions is denied
access to chart endpoints not in their allow-list. The chart endpoint is
included in the test catalog (matching production), so the test
exercises the allow-list rejection path.

All 17 tests in the auth middleware package pass.

### Local verification

1. Confirmed the chart middleware in `cmd/fleet/serve.go` previously
called `auth.AuthenticatedUser(svc, next)` without
`APIOnlyEndpointCheck` wrapping
2. Verified the activity bounded context (same file) already uses
`auth.APIOnlyEndpointCheck(next)` as the correct pattern
3. Applied the same wrapping to the chart middleware
4. Added `RouteTemplateRequestFunc` to
`server/chart/internal/service/endpoint_utils.go` so the route template
is available in context (required by `APIOnlyEndpointCheck`)
5. Ran `go test ./server/service/middleware/auth/ -v` with all 17 tests
passing
6. Ran `make lint-go-incremental` with 0 issues
2026-07-23 10:44:13 -04:00
RachelElysia 968ea20aeb Fleet UI: Searchable fleets dropdown with add-fleet affordance (#49690) 2026-07-23 07:26:04 -07:00
9d6f25acd7 Make MFA token redemption atomic to prevent multiple sessions
Resolves #16770

The MFA login token redemption path (`POST /api/latest/fleet/sessions`)
read the one-time verification token with a non-locking `SELECT` on the
read replica, then created a session and deleted the token in a
*separate* transaction without verifying the token was still present.
Concurrent requests carrying the same token each passed the `SELECT` and
each minted a distinct session, breaking the single-use guarantee.

`SessionByMFAToken` now consumes the token and creates the session
inside a single transaction:

- The token row is locked with `SELECT ... FOR UPDATE`, then deleted,
and the delete's rows-affected count is confirmed non-zero before the
session is created.
- Concurrent redemptions serialize on the row lock; the loser re-reads
after the winner commits the delete, finds no row, and aborts before
creating a session.
- The user is still loaded *before* the transaction, so a
concurrently-deleted user or a transient read error leaves the token
intact for retry (preserving the pre-fix atomicity behavior).

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Juan Fernandez <juan@fleetdm.com>
2026-07-23 09:52:07 -04:00
Steven Palmesano 62ed3583e6 Clear button styles (#49292)
**Related issue:** Resolves #49276

**New features**
- Added new "Secondary" (bordered, off-white fill) and "Subdued"
(borderless, low-emphasis) button variants to match the Figma spec,
alongside the existing Primary style.
- Allowed rows to be selected in Controls > OS updates.

**Cleanup**
- Once nothing referenced the old styles anymore, fully removed the old
`text-icon`, `brand-inverse-icon`, `inverse-alert`, `inverse`, and
`icon` button variants (type, styles, and Storybook entries) from the
shared `Button` component.
- Removed the `iconStroke` prop, which had become a no-op once the old
variants it supported were gone.
- Renamed `ActionsDropdown`'s variants
(`button`/`brand-button`/`small-button`) to
`subdued`/`primary`/`secondary` to match the same naming used everywhere
else.
- Replaced a one-off dropdown implementation on the Software title page
with the shared `ActionsDropdown` component, instead of maintaining
duplicate styling logic.
- Changed the button name on Host details > Reports > Report details
from "View data for all hosts" to "View report for all hosts" (to match
the previous page's Actions drop-down options).


# 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

<img width="1475" height="241" alt="Screenshot 2026-07-21 at 06 35 49"
src="https://github.com/user-attachments/assets/7cfbd444-7837-40e8-854e-bc5989d57d85"
/>
<img width="661" height="306" alt="Screenshot 2026-07-21 at 06 37 18"
src="https://github.com/user-attachments/assets/5d0c4873-8179-4089-b115-7e8cd3a53b4d"
/>
<img width="1427" height="423" alt="Screenshot 2026-07-21 at 06 37 30"
src="https://github.com/user-attachments/assets/a4850a60-f44a-4902-b45e-0094f23a52f8"
/>
<img width="1427" height="640" alt="Screenshot 2026-07-21 at 06 37 46"
src="https://github.com/user-attachments/assets/738a4a7f-cd7d-4162-b659-6f649c32204d"
/>
<img width="1445" height="479" alt="Screenshot 2026-07-22 at 07 03 22"
src="https://github.com/user-attachments/assets/4f672dc0-5c6d-4eb8-8465-ed5233fcd1b2"
/>
<img width="811" height="871" alt="Screenshot 2026-07-21 at 06 41 20"
src="https://github.com/user-attachments/assets/5421c96e-2dab-492a-af26-be0e5a7791ca"
/>
2026-07-23 07:11:59 -05:00
Juan Fernandez fbccb8cc59 Emit created/deleted activities for setup experience scripts
Setup experience script add/replace/delete now record activities (API
and GitOps), skipping no-op re-submissions.
2026-07-23 06:41:51 -04:00
Juan Fernandez e91a0b2987 Normalize login responses for MFA-enabled accounts
Make failed logins for MFA-enabled accounts return a consistent response
and timing regardless of the cause, in line with authentication best
practices. Guidance for CLI users whose client can't complete email
verification is now surfaced by fleetctl on any login failure.

Added a `user_mfa_requested` activity, recorded when valid credentials
are submitted for an MFA-enabled account and a verification email is
sent.
2026-07-23 06:41:27 -04:00
Juan Fernandez d903ec58e1 Fix label update consistency issue
Persist label metadata and membership changes together in a single
transaction so a failed update can't leave a partial change behind.
2026-07-23 06:41:07 -04:00
Victor Lyuboslavsky a7eb747faf Flag to bypass end user auth (#49683)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #46644 

Demo video: https://www.youtube.com/watch?v=svCaA-820yc
Docs: https://github.com/fleetdm/fleet/pull/49713/changes

# 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

## fleetd/orbit/Fleet Desktop

- [x] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [x] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [x] Verified that fleetd runs on macOS, Linux and Windows
  - Did not verify macOS.
- [x] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added `--bypass-end-user-auth` to `fleetctl package` and Orbit.
* Generated Linux and Windows installers can skip the end-user
authentication prompt during enrollment.
* Added `ORBIT_BYPASS_END_USER_AUTH` for environment-based
configuration.
* End-user authentication remains enabled when a supported EUA token is
provided.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 15:32:58 -05:00
Victor Lyuboslavsky 95b535a622 Reverting printableCharacters SCEP validation (#49758)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49756

# 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **Bug Fixes**
* Custom SCEP proxy challenges can again include characters such as
underscores.
  * Apple device enrollment works again with these challenges.
* Removed the overly strict printable-character validation from the
Custom SCEP configuration form.
* The Challenge field now only enforces the required-value rule and no
longer shows printable-character validation errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 13:46:47 -05:00
Magnus Jensen b9136f4da5 Release from AB backend support (#49680)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49367 

# 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] 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

* **New Features**
* Added “Release from Apple Business” for eligible Apple hosts,
including per-device success/failure reporting and activity logging.
* Added a new API endpoint to trigger the action and return results for
each selected host with clear error details.
* Introduced authorization rules for global admins and team admins to
release only within allowed scope.
* **Bug Fixes**
* Improved validation and error handling: rejects oversized selections,
reports unknown/ineligible hosts and DEP-related failures per device,
and treats assignment-cleanup failures as non-blocking.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 20:01:41 +02:00
Magnus Jensen ccaea1373b trim MDM SSO whitespace in GitOps and API (#49378)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48003 

# 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**
- Fixed MDM SSO configuration handling to automatically remove leading
and trailing whitespace from provider fields.
- GitOps-applied MDM SSO settings are now normalized before validation,
preventing otherwise invalid configurations caused by extra spaces.
  - Required-field and URL validation now operate on the cleaned values.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 19:19:48 +02:00
Juan Fernandez 10ac3c73a3 Harden password reset token handling
Ensure a password reset token can only be used once.
2026-07-22 12:46:16 -04:00
Nico e017eb6176 Allow bypassing network blocking in production via config (#49747)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49751

A customer's egress proxy (an Envoy sidecar bound to loopback) was
getting blocked by Fleet's SSRF network-blocking check, since the check
applies to whatever address the HTTP transport dials, including the
proxy hop itself, not just the ultimate destination. There was no
supported way to disable this in production (the existing full-bypass
mode was dev-only), leaving no path forward for environments where
egress is already constrained by external infrastructure.

# 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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a production server setting to bypass outbound network blocking
for integration requests when external egress controls are already in
place.
* The setting is disabled by default and can be configured through the
server configuration.

* **Documentation**
* Clarified that bypassing network blocking disables SSRF protections
for all outbound integration requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 13:23:21 -03:00
Dante Catalfamo 932e82e856 Gitops mode disables android MDM connect and turn off buttons (#49685)
**Related issue:** Resolves #48226
2026-07-22 11:31:27 -04:00
LeAnn 8677e1cf53 Fix Add API-only user > Specific API endpoints table search (#49613)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49707

# 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

* **New Features**
* Improved API endpoint search with relevance-based ranking across
endpoint names and paths.
* Search results now prioritize exact, prefix, whole-word, and partial
matches.
  * Added path-based matching and clearer empty-state behavior.
  * Removed pagination in favor of the existing results scrollbar.

* **Bug Fixes**
* Preserved relevance ordering instead of applying an incorrect default
sort.
  * Excluded already-selected endpoints from search results.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 08:10:02 -07:00
Dante Catalfamo 6d0ecf1b9f Clarify which OS supports OS Updates (#49716)
**Related issue:** Resolves #48960
2026-07-22 10:48:28 -04:00
Victor LyuboslavskyandSharon Katz f04430a5a6 Added redirect_uri validation for Windows TOS endpoint. (#49699)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves
https://github.com/fleetdm/confidential/issues/16880

# 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**
* Secured the Windows MDM Terms of Service endpoint against reflected
cross-site scripting.
* Strengthened validation for the `redirect_uri` used in Terms of
Service rendering, allowing only approved `https` and `ms-appx-web`
schemes.
* Unsafe, malformed, or non-allowlisted redirect values are now rejected
and not displayed.

* **Tests**
* Added integration and unit coverage to verify unsafe redirects are
blocked while valid ones continue to work.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com>
2026-07-22 09:24:40 -05:00
George Karr 3b32a526ee Fix 500 on Apple MDM enroll when host has no DEP assignment (#47963) (#49623)
**Related issue:** Resolves #47963

# 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.
- [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
- [ ] QA'd all new/changed functionality manually

## Summary

Fixes a 500 seen via monitoring during `POST /api/mdm/apple/enroll`:

```
checking os updates settings serial [redacted]: getting team id for host: sql: no rows in result set
```

### Root cause

During DEP enrollment, `CheckMDMAppleEnrollmentWithMinimumOSVersion` →
`shouldOSUpdateForDEPEnrollment` calls
`GetMDMAppleOSUpdatesSettingsByHostSerial`, which joins `hosts` to
`host_dep_assignments` by serial. When no matching row exists yet — e.g.
the enrollment request arrives before the host / DEP assignment row is
created or replicated (replica lag / ordering) — `sqlx.GetContext`
returns `sql.ErrNoRows`.

The service layer already handles this case gracefully (skip the
OS-update check, allow enrollment to proceed) via
`fleet.IsNotFound(err)`. But the datastore wrapped the raw
`sql.ErrNoRows` with a plain `ctxerr.Wrap`, which does not implement the
`IsNotFound()` interface, so the graceful path never triggered and the
request 500'd.

### Fix

Convert `sql.ErrNoRows` into a proper `notFound` error in the datastore
method, matching the existing pattern used throughout `apple_mdm.go`.
This lets the existing service-layer graceful-skip path take over so
enrollment proceeds.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed Apple MDM enrollment to continue gracefully when OS update
settings are missing because a host’s DEP assignment hasn’t been created
yet or hasn’t replicated.
* Prevented enrollment from failing with an unexpected 500 error by
returning a clear “not found” outcome instead.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 09:18:57 -05:00
Magnus Jensen ba0b1c3bea Fix resend button not showing on keyboard navigation in OS settings modal (#49728)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves none

After:


https://github.com/user-attachments/assets/cc9b73be-a015-49dd-adc3-5b516c02ea4c


Before:


https://github.com/user-attachments/assets/e870bc22-5984-4076-bb21-35699eda45bf

# 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

* **Bug Fixes**
  * Improved keyboard accessibility in OS settings tables.
* Resend and rotate actions now appear when their table row receives
keyboard focus, in addition to mouse hover.
* **Documentation**
  * Added a change note describing the accessibility improvement.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 16:12:29 +02:00
Nico edf4a45df9 Stop the delete host endpoint from revealing out-of-fleet host existence (#49645)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves: N/A

# 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Updated host deletion behavior to return consistent “not found”
responses when the host doesn’t exist or isn’t visible to the requester.
* Prevented out-of-scope delete attempts from disclosing whether the
target host exists (now returns “not found” instead of “forbidden”).
* Preserved “forbidden” errors when the host is visible but the
requester lacks delete permission.
* **Tests**
* Added/updated authorization and deletion coverage to verify the new
response-masking behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 11:12:16 -03:00
Nico 87c1a719c1 Support custom host vitals in host name templates (#49586)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49489

Custom host vitals were skipped when host name template enforcement
(#38806) shipped, since both features were in development at the same
time. This adds `$FLEET_HOST_VITAL_<id>` support to host name templates,
matching the existing secret-variable pattern (validation, per-host
resolution, resend on value change).

I also introduced a new `IsInvalidReferencedCustomHostVitalsError` call
after Copilot's comment below.

# 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Added support for `$FLEET_HOST_VITAL_<id>` in Apple host name
templates.
* Device-name template reconciliation now expands referenced per-host
vital values and updates automatically when those values change.

* **Bug Fixes**
* Prevents deleting custom host vitals that are referenced by host name
templates.
* If a referenced vital has no value for a host, device-name delivery is
marked failed for that host (retryable).

* **Improved Error Handling**
* Refined validation behavior so unknown/malformed vital references
return user-facing invalid-argument errors, while infrastructure errors
propagate unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 09:38:24 -03:00
Rajendra Kadam e7a0d702dd Generate CPE for Firefox Developer Edition on macOS
Resolves #48689

Adds a CPE translation so Firefox Developer Edition on macOS resolves to
the standard `mozilla:firefox` product. Without it, `CPEFromSoftware`
generates no CPE for the app, so it matches no Firefox CVEs and shows as
vulnerability-free — a silent false negative.

The root cause is that none of the standard matching paths fit Developer
Edition: its bundle identifier `org.mozilla.firefoxdeveloperedition`
splits to a product token (`firefoxdeveloperedition`) that has no NVD
entry, and the sanitized-name and full-text fallbacks don't resolve to
`firefox` either. Regular Firefox works only because its bundle
(`org.mozilla.firefox`) splits cleanly to `mozilla`/`firefox`. The fix
uses the same translation mechanism the existing Firefox ESR rule uses,
mapping the Developer Edition bundle to `product: firefox`, `vendor:
mozilla` — with no `sw_edition`, since Developer Edition tracks standard
Firefox advisories (ESR is the special case that needs the `esr`
edition).

Match is on the bundle identifier rather than the display name so it's
stable regardless of how the app name is ingested.

**Out of scope:** Firefox Nightly (`org.mozilla.nightly`) has the same
failure mode but uses pre-release version strings (e.g. `155.0a1`) that
don't line up with NVD's per-version Firefox CPEs, so mapping it to
`firefox` risks bad matches — it warrants separate handling. Firefox
Beta already works today (its bundle is `org.mozilla.firefox`), so it
needs no change.

**Testing.** Two layers, matching how the codebase already tests CPE
rules:
- An offline unit test (`TestFirefoxDeveloperEditionTranslation`) loads
the real shipped `cpe_translations.json` and asserts Developer Edition
translates to `mozilla:firefox` with no `sw_edition`. It needs no CPE
dictionary or network, so it runs in the fast suite.
- A case in the network-gated `TestCPEFromSoftwareIntegration`,
alongside the existing regular-Firefox case, asserts the full CPE string
against the live NVD dictionary in CI. It reuses the known-good
`105.0.1` Firefox entry. The downstream CPE→CVE step is unchanged and
already covered for `mozilla:firefox` by `TestTranslateCPEToCVE`, so no
new CVE-matching test is needed.

# 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 — ran
`TestFirefoxDeveloperEditionTranslation` locally against the shipped
rule (passes); the full software→CPE resolution against live NVD data is
exercised by the network-gated integration case in CI.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved recognition of Firefox Developer Edition on macOS so it maps
to the expected Firefox vulnerability data.
* Better handling of version matching for this app, helping scan results
stay accurate.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 08:04:38 -04:00
Juan Fernandez f89cde43b6 Harden user creation from invite
Fixed an issue where an SSO-only invitation could be accepted with a 
password, creating a local password-authenticated account and 
bypassing SSO enforcement. The authentication mode is now derived 
solely from the invite. Derive the authentication mode from the invite 
record instead of client input during invite acceptance.
2026-07-22 07:29:15 -04:00
Sharon Katz d9426402b2 Normalize LocURI values before validation in Windows profiles (#49708)
**Related issue:** Resolves fleetdm/confidential#16883

# 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

## Summary

Normalized LocURI target values before validation checks in Windows MDM
profile handling.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
- [x] Confirmed that the fix is not expected to adversely impact load
test results

### Reproduction

Wrote test cases that construct Windows SCEP profile XML with trailing
whitespace appended to LocURI paths (e.g., `/Install/SubjectName ` with
a trailing space). Before the fix, these profiles passed validation
without the required renewal-id marker because `strings.HasSuffix` did
not match the whitespace-suffixed path. The same bypass applied to
Challenge and ServerURL LocURIs.

### Unit tests added

7 new test cases across two test functions:

**`TestAdditionalNDESValidationForWindowsProfiles`** (3 new cases):
- SubjectName LocURI with trailing whitespace is still validated for
renewal id
- Challenge LocURI with trailing whitespace still validates correctly
- ServerURL LocURI with trailing whitespace still validates correctly

**`TestAdditionalCustomSCEPValidationForWindowsProfiles`** (new
function, 4 cases):
- Valid custom SCEP profile passes
- SubjectName missing renewal id is rejected
- SubjectName with trailing whitespace in LocURI is still validated for
renewal id
- SubjectName with internal whitespace (not trailing) is rejected

### Local verification

1. Wrote failing tests first, confirmed the whitespace bypass existed
(tests failed as expected before the fix)
2. Applied the fix (`strings.TrimSpace` on target before `HasSuffix`
checks)
3. Confirmed all new tests pass after the fix
4. Ran full test suite: `go test ./server/service/ -run
"TestAdditionalNDESValidation|TestAdditionalCustomSCEPValidation" -v`
with all 14 tests passing
5. Ran `make lint-go-incremental` with 0 issues
2026-07-21 17:20:53 -04:00
Dante Catalfamo 4d924e2f48 Show managed Android serial number on Hosts page (#49711)
**Related issue:** Resolves #48379
2026-07-21 16:50:46 -04:00
Magnus Jensen 94d5a7d27c create new appCfg entry on AB fleet updates if not found (#49559)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48653 

# 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**
- Improved Apple Business Manager configuration handling when updating
team assignments, including creating the assignment entry when it
doesn’t already exist.
- Removing an Apple Business Manager token now also removes its
corresponding assignment details and properly updates configuration
status when no tokens remain.
- Prevented `generate-gitops` from exporting an empty `apple_business`
section when default fleets are configured only via the UI.
- **Tests**
- Expanded coverage for team assignment updates to validate creation of
new configuration entries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-21 21:33:36 +02:00
Magnus Jensen 966838b159 Don't queue profiles for non host_mdm.enrolled Apple hosts (#49611)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48845 

# 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**
* Apple MDM reconciliation no longer queues profiles for deleted or
non–MDM-enrolled hosts.
* Apple MDM reconciliation batching/snapshots now include only hosts
with confirmed active MDM enrollment, reducing incorrect or stale
reconciliation candidates.
* **Tests**
* Added MySQL datastore coverage to validate reconcile snapshot
selection and reconcile host lookup behavior.
* Improved Apple MDM and related test setups to explicitly ensure
required MDM server configuration exists before reconciliation
assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-21 21:33:29 +02:00
Juan Fernandez ff7955c0f0 Fix race condition in one-time software installer download token
Token redemption consumed the key via a non-atomic read-then-delete,
which allowed concurrent requests to redeem the same one-time token more
than once. Made consumption atomic so a token can only be used once,
even under concurrent access, and added coverage for the concurrent
path.
2026-07-21 11:33:46 -04:00
Juan Fernandez a156079d55 Fix SCIM deprovisioning edge case during user deactivation
Resolve the matching Fleet user from the persisted SCIM record rather
than the incoming request state when handling deactivation, so
deprovisioning still works when identifiers change in the same request.
2026-07-21 11:33:08 -04:00
Juan Fernandez 9f251c21fe Fix SCIM middleware persisting authorization failures to last_request
The LastRequestMiddleware already skipped 401 responses but not 403s, so
unauthorized users could overwrite the admin-visible SCIM status. Skip
both.
2026-07-21 11:32:36 -04:00
Victor Lyuboslavsky e14ef67b55 Fix Windows Autopilot ESP hang: gate release on user-scope ack (#49134) (#49542)
The ESP release wrote the user-scope ServerHasFinishedProvisioning
Replace and immediately committed awaiting_configuration=None. During
OOBE the device rejects user-scope writes with SyncML 405 until its user
MDM context initializes, so the Account setup phase never received its
completion signal and the device hung until the 3-hour timeout, while
Fleet believed the ESP had completed (and relaxed the DMClient poll,
crippling remediation).

The release path now stays Active until the device acks the user-scope
Replace with a 200: a new resend phase re-sends the Replace once per
session (bounded by the existing 3-hour timeout), and the Active->None
transition commits only on the 200.

Live-validated on Win11 26200 on both a fresh and a re-enrolled device:
the 405 reproduced at release time in both flows, and the retried
Replace acked 200 one session after the user context came up, releasing
the ESP.

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49134

# 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] Timeouts are implemented and retries are limited to avoid infinite
loops

## 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
- Fixed an issue where Windows Autopilot enrollment could intermittently
hang at **“Account setup”** on the Enrollment Status Page.
- Updated Enrollment Status Page release handling so enrollment **stays
active until the device acknowledges** the user-scope completion
command.
- When the user-scope completion is rejected or still unacknowledged, it
is **retried in subsequent management sessions** until successfully
acknowledged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-21 09:03:54 -05:00
Andrew MellorandJordan Montgomery 15a0f4b201 48342 edit config profile endpoint (#49141)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #48342

# 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] QA'd all new/changed functionality manually



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added support for editing existing Apple, Windows, and Android
configuration profiles through the API.
* Supports updating profile content, names where applicable, label
targeting, and Fleet variable associations without replacing the profile
identity.
  * Added support for editing Apple DDM declarations.
  * Added activity tracking for configuration profile edits.
* **Bug Fixes**
* Added validation for unsupported edits, invalid labels, duplicate
names, missing profiles, and protected Fleet-managed profiles.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Jordan Montgomery <elijah.jordan.montgomery@gmail.com>
2026-07-21 08:20:03 -04:00
Nico 900c54e822 Fix label membership being cleared when a label query errors (#49403)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #46399

When a label's query errors on a host (e.g. the extension socket is
unavailable) instead of returning zero rows, Fleet was recording that
error the same as a definitive "no match," clearing the host's existing
label membership. This could unintentionally remove configuration
profiles or other automations scoped to that label. The fix leaves
existing label membership untouched when a label query errors.

# 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

**Setup:** macOS VM enrolled as a Fleet host, with a dynamic label whose
query targets a real, always-present table but with a deliberately
invalid `WHERE` clause, so the query fails deterministically (a `no such
column` SQL error).

```sql
-- working version (label matches)
SELECT * FROM os_version;

-- broken version (query errors on every run)
SELECT * FROM os_version WHERE this_column_does_not_exist = 1;
```

### Before (bug reproduced on unpatched code)

1. Set the label's query to the working version and refetched the host —
confirmed it shows up under the host's Labels.
2. Edited the label's query to the broken version.
3. Clicked **Refetch** on the host.
4. **Result:** the label disappeared from the host's Labels list — a
query error incorrectly cleared existing membership.

### After (fix verified)

1. Reset the label's query to the working version and refetched —
confirmed membership was restored.
2. Edited the label's query to the broken version again.
3. Clicked **Refetch** on the host.
4. **Result:** the label remained on the host's Labels list — a query
error now correctly leaves existing membership untouched.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Preserved existing dynamic label memberships when label queries fail
or yield unknown results.
  * Avoided treating unknown/failed evaluations as label removals.
* Ensured label updates/removals are applied only when a definite match
or non-match is returned.
* **Tests**
* Expanded coverage for label query errors across datastore, async
processing, and distributed execution to confirm memberships remain
unchanged.
* Updated expectations for queued async updates to skip errored labels.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-20 10:31:09 -03:00
Carlo 7cb2399700 Redirect FMA installs to the active version after auto-update (#49525)
**Related issue:** Resolves #49495

Redirects queued Fleet-maintained app installs to the newly-active
installer (canceling already-dispatched ones) atomically when an
auto-update or pin change flips the active version, and re-resolves
install retries to the active installer, so a host no longer installs a
superseded cached version.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `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

* **Bug Fixes**
* Fleet-maintained app installs now consistently use the currently
active version after automatic promotions, preventing stale installer
targeting.
* Queued installs tied to an older promoted installer are redirected to
the newly active installer instead of being canceled.
* Install retry flows now re-resolve to the active installer at retry
time, avoiding stale retries after version changes.
* **Tests**
* Added datastore coverage for active-installer redirection and updated
retry tests to verify the correct installer ID is used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-18 06:01:45 -04:00
Jonathan Katz 8a33fcd058 Fix FMA pinning not changing patch policy (#49519)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #49474

# 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
- Relied on integration test for testing changes made by the
`maintained_apps_auto_update` job


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed patch policies for Fleet-maintained apps not updating when the
active app version changes.
* Patch policy queries now refresh to match the currently active (or
pinned) installer version, including changes driven by pinning, cron,
and GitOps.
* Improved behavior when pins are cleared or switched, ensuring the
policy continues referencing the correct version-specific query.
* **Tests**
* Expanded integration coverage to verify version-pinned patch policy
queries across scenario updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 19:00:13 -04:00
Carlo 8cd9503267 Fix false-success reporting for failed software installs (#49515)
**Related issue:** Resolves #49475

Makes a non-zero install-script exit code a terminal failure so an
install that failed but whose post-install script exited 0 is no longer
reported as installed, in both the Go status computation and the
`host_software_installs` `status`/`execution_status` generated columns.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `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

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration. Redefining the
`status`/`execution_status` generated columns rebuilds the table, but
`ON UPDATE CURRENT_TIMESTAMP` is not triggered by `ALTER TABLE`, so
`updated_at` is preserved.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Installations that fail during the install script are now correctly
reported as failed, even if the post-install script succeeds.
* Install and execution status reporting is now consistent about which
script exit code takes precedence.
* Pending, successful, failed, canceled, and uninstall outcomes continue
to be reported correctly.
* **Tests**
* Added regression/unit test coverage for install-status and
execution-status precedence across mixed install/post-install exit code
scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 17:59:14 -04:00
LeAnn 13224b0660 Update host vitals refresh error banner (#49526)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #38214

# 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 test
- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Clarified the notification shown when host vitals take longer than
expected to load.
* Messages now confirm that a refetch request was sent and that the
display will update once the host responds.
* Updated notifications across host welcome, device details, and host
details views.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 14:53:00 -07:00
Dante Catalfamo 67c06a9820 Mask team enroll secrets in team write responses (#49422) 2026-07-17 16:45:40 -04:00
Lucas Manuel Rodriguez da3f30df79 Allow Microsoft conditional access on premium self-hosted (#49414)
Resolves #47699.

- [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**
* Microsoft Entra Conditional Access is now supported for self-hosted
Fleet Premium instances.
* Conditional Access is available only on the Fleet Premium license
tier.
* **Changes**
* Removed the Microsoft Compliance Partner API key configuration and
updated the proxy behavior accordingly.
* Removed the managed-cloud indicator from license/config responses and
adjusted related UI rendering and gating.
* **Tests / Maintenance**
* Updated fixtures and automated tests to reflect the new licensing
gates and API/proxy behavior (including updated failure codes).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 10:59:32 -03:00
Juan Fernandez d2210243fc Rejected empty and whitespace-only enroll secrets when creating or updating teams
Rejected empty and whitespace-only enroll secrets when creating or updating teams
2026-07-16 18:54:38 -04:00