**Related issue:** Resolves#35172
Updates the macOS 14 (Sonoma) CIS policy set to benchmark v3.0.0, adds a
`cis_id` field to every policy, fixes several broken test scripts,
introduces an automated test runner, and ships `CIS-BENCHMARKS.md` as a
central guide for authoring and maintaining CIS benchmarks.
## Summary of changes
- `ee/cis/macos-14/cis-policy-queries.yml`: v3.0.0 updates + `cis_id`
added to every entry
- `ee/cis/CIS-BENCHMARKS.md`: new authoring/testing/automation guide for
all macOS CIS benchmarks (and the pattern other OS dirs follow)
- `tools/cis/cis-test-runner.py`: new 2150-line Python runner that
drives end-to-end validation against a real tart VM + Fleet server
- `ee/cis/macos-14/test/scripts/`: 10 new pass/fail script pairs, 8
existing scripts fixed (several were silently broken)
# How the automated testing works
The runner (`tools/cis/cis-test-runner.py`) exercises the full policy
lifecycle against a real macOS VM:
**Phases**
1. **Parse** `cis-policy-queries.yml` and filter by `--all`,
`--cis-ids`, `--match`, and type flags (`--only-scripts`, `--only-mdm`,
`--only-manual`).
2. **Classify** each policy into a test type based on available
artifacts:
| Priority | Type | Artifacts | Behavior |
|---|---|---|---|
| 1 | `PASS_FAIL` | `CIS_{id}_pass.sh` + `_fail.sh` | Run fail → verify
query fails → run pass → verify passes |
| 2 | `PASS_ONLY` | `CIS_{id}.sh` | Run script → verify passes |
| 3 | `PROFILE` | `.mobileconfig` only | Verify query fails before
profile → push profile → verify passes |
| 4 | `ORG_DECISION` | paired `-enable`/`-disable` profiles | Toggle
between variants |
| 5 | `MANUAL` | none | Prompt operator, or skip with `--skip-manual` |
3. **Provision**: create a fresh Fleet team with a unique enroll secret,
build a fleetd pkg bound to it, create+boot a tart VM, install the
agent, and enroll.
4. **MDM**: prompt the operator for MDM enrollment if any tests need it.
Clear team profiles, baseline the VM, push all required profiles in one
batch, wait for delivery.
5. **Execute**: for each plan, SCP the script, run it over SSH, then run
the policy via `fleetctl query --hosts <hostname>`. A query that returns
rows = pass.
6. **Report** summary with PASS/FAIL/SKIP/ERROR counts.
7. **Cleanup** (with `--cleanup`) deletes the team, host record, and VM.
**Special-case handling** (keyed by OS version because CIS IDs aren't
stable across releases):
- `SSH_BREAKING_CIS_IDS`: tests that disable sshd (2.3.3.4, 2.3.3.5) are
forced to `MANUAL` so the runner doesn't lock itself out.
- `PASSWORD_POLICY_CIS_IDS`: 5.2.x profiles invalidate the VM's
`admin`/`admin` login — forced to `MANUAL`.
- `NON_AUTOMATABLE_CIS_IDS`: tests that can't run reliably in a VM
(Location Services, Touch ID, shared Siri profile state) forced to
`MANUAL` with a per-entry reason.
- `--keep-vm`: reuses the VM across runs, skipping agent
install/enrollment if the host is already in Fleet. Falls back to fresh
creation if SSH is unreachable.
**Credential resolution order**: CLI flag →
`FLEET_URL`/`FLEET_API_TOKEN` env → `~/.fleet/config` (from `fleetctl
login`).
## How to use `CIS-BENCHMARKS.md` going forward
The doc is the single reference for authoring and maintaining CIS
benchmark policies across all macOS (and Windows) versions. For each new
benchmark release, the workflow is:
1. **Read "Updating benchmarks when a new CIS version is released"** —
directs you to the PDF's *Appendix: Change History* to enumerate
Added/Modified/Removed recommendations.
2. **Use the field reference and query patterns** to write or update
policies: direct table check, `managed_policies` EXISTS/NOT EXISTS, or
plist negation check. Name qualifiers `(MDM Required)` / `(Fleetd
Required)` / `(FDA Required)` are documented.
3. **Create matching test artifacts** — pass/fail scripts for togglable
settings, `.mobileconfig` profiles for MDM-only settings. Script
conventions (full paths, sudo pattern, `not_always_working_` prefix) are
standardized.
4. **Update the per-OS README** with limitations, org-decision policies,
and optional policies.
5. **Run the test runner** to validate.
The doc also contains an **end-to-end AI agent prompt** (section at the
bottom) designed to be handed a new CIS PDF plus the previous version's
PDF, to automatically generate the diff, write policies, produce test
artifacts, update docs, and run validation. This lets future benchmark
updates start from a consistent, repeatable baseline rather than being
hand-authored from scratch.
## Query changes
All entries in `cis-policy-queries.yml` received a `cis_id` field so the
runner (and humans) can map policies → scripts → profiles → the
benchmark document without parsing the display name.
| CIS ID | Change |
|---|---|
| 1.1 | Renamed "Ensure All Apple-provided Software Is Current" →
"Ensure Apple-provided Software Updates Are Installed"; added terminal
remediation to `resolution` |
| 1.6 | Expanded description with v3.0.0 language about rapid security
response updates |
| 1.x (deferment) | **Removed** — "Ensure Software Update Deferment Is
Less Than or Equal to 30 Days" dropped from v3.0.0 |
| 2.3.1.1 | Renamed "Ensure AirDrop Is Disabled" → "Ensure AirDrop Is
Disabled When Not Actively Transferring Files"; expanded description |
| 2.3.3.1 (DVD/CD Sharing) | **Removed** — dropped from v3.0.0 |
| 2.3.3.4 (Remote Login) | Query now checks BOTH `disabled.plist` AND
that `com.openssh.sshd` is not in the `launchd` table; resolution
updated to terminal method |
| 5.1.7 | Query rewritten: sticky-bit dirs now properly excluded
(first-char-of-mode check instead of bit-AND on full mode string);
SIP-protected dirs excluded via `com.apple.rootless` xattr check |
| 6.3.1, 6.3.2, 6.3.3, 6.3.4, 6.3.7 | Dropped `username = ''` filter —
Safari profiles deliver at user scope, so the system-scope filter
guaranteed zero rows |
| 6.3.3 | Fixed `NOT EXISTS` domain typo: `com.apple.loginwindow` →
`com.apple.Safari` (the check was previously meaningless) |
| Wi-Fi/Bluetooth menu bar | **Removed** — "Show Wi-Fi status in Menu
Bar" and "Show Bluetooth status in Menu Bar" dropped from v3.0.0 |
| Show All Filename Extensions | **Removed** — dropped from v3.0.0 |
## Script changes
### New scripts
| Script | Purpose |
|---|---|
| `CIS_1.1_pass.sh` / `_fail.sh` | Install updates (pass); clear
`LastFullSuccessfulDate` (fail — caveat: only works when real updates
are pending) |
| `CIS_1.6_pass.sh` / `_fail.sh` | Open/remove the `1.6.mobileconfig`
profile |
| `CIS_2.3.1.1_pass.sh` / `_fail.sh` | Open/remove the AirDrop profile |
| `CIS_2.3.2.2_pass.sh` / `_fail.sh` | `launchctl load/unload -w` of
`com.apple.timed.plist` |
| `CIS_2.3.3.4_pass.sh` / `_fail.sh` | `systemsetup -setremotelogin
off/on` (runs as MANUAL via the runner's SSH-breaking safeguard) |
### Existing scripts fixed
| Script | Bug | Fix |
|---|---|---|
| `CIS_2.3.3.1.sh` | Disabled `com.apple.ODSAgent` (DVD/CD sharing), not
Screen Sharing | Now disables `com.apple.screensharing` |
| `CIS_2.9.2.sh` | `pmset -a womp 0` sets Wake-on-Network, not Power Nap
| Now `pmset -a/-b/-c powernap 0` |
| `CIS_3.2.sh` | `sed` pipeline into root-owned file via user
redirection silently failed; did nothing if the flags line was missing |
`awk` with `tee`/`mv`; appends a flags line when absent; enforces 0400
root:wheel |
| `CIS_3.3.sh` | Only stripped `all_max=`; never added `ttl=365` when
missing, so the query could never pass from a fresh system | `awk` now
both strips `all_max=` and inserts/updates `ttl=365` on the `* file`
line |
| `CIS_3.4.sh` | `sudo sed … > /etc/security/audit_control` — redirect
runs as caller, not root; write silently failed | Rewrites via
`tee`/`mv` with proper perms; appends when line is absent |
| `CIS_3.5.sh` | `chmod -R o-rw` doesn't produce the exact `0400`/`0440`
modes the query requires | Explicit `chmod 0400` on `audit_control`,
`find … -exec chmod 0440 {}` under `/var/audit/` |
| `CIS_5.1.7.sh` | `sudo IFS=$'\n'` runs IFS in a subshell that exits
immediately; searched `/System/Volumes/Data/Library` but the query looks
at `/Library/%` | IFS set in parent shell; searches `/Library`; skips
SIP-protected dirs via xattr |
| `CIS_5.7.sh` | Wrote `use-login-window-ui` which the query doesn't
accept | Writes `authenticate-session-owner` |
| `CIS_6.3.6.sh` | Contained literal `<username>` placeholders that were
never substituted | Iterates non-system users (`UniqueID >= 500`) and
runs `defaults write` as each |
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* macOS 14 (Sonoma) CIS policies updated to v3.0.0 with refreshed policy
names and CIS IDs.
* New CLI test runner to automate CIS validation against macOS VMs.
* **Bug Fixes / Improvements**
* Updated remediations and audit/query logic; safer, atomic config
updates; several policies revised or removed.
* **Tests**
* Many new and improved pass/fail helper scripts for validating CIS
checks and profiles.
<!-- 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#38013
# 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
- [x] QA'd all new/changed functionality manually
See
https://drive.google.com/file/d/1zeFNLuf_rT5FWzDiYyL2_hbIBW2neba-/view?usp=drive_link
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* GitOps variables in JSON configuration profiles (Apple DDM
declarations and Android profiles) are now automatically escaped for
JSON special characters, ensuring proper handling of sensitive values.
* **Tests**
* Added JSON configuration profile escaping validation to the enterprise
GitOps integration test suite.
<!-- 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#43977
# 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
- [ ] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
- Repro'd issue on main
- Verified on this branch that dirtying the form and then focusing
elsewhere / refocusing on page doesn't lose changes.
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
* Fixed an issue where the Change Management form would reset upon
losing and regaining page focus.
<!-- 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#41676
# 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
* **Bug Fixes**
* Apple MDM APNS certificate signing now shows a clear, domain-specific
error when an unsupported email domain is supplied (applies to CSR
requests and renewal flows), replacing the previous generic "invalid
email" message.
<!-- 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#40856
# 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
* **Bug Fixes**
* EULA PDF upload size validation now reports the configured maximum
upload size (in MiB) instead of a fixed value, improving clarity of
rejection messages.
* **Tests**
* Added tests covering EULA upload size validation, ensuring oversized
uploads are rejected with the proper status and that configured
request-body limits are respected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated version metadata for 14 applications: Adobe Creative Cloud,
Bruno, Connect Fonts, Google Chrome, IntelliJ IDEA, Krita, Loom, MongoDB
Compass, MySQL Workbench, Notion, Postman, Proxyman, Visual Studio Code,
and Webex, including installer references and integrity verification
information.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: mostlikelee <16102903+mostlikelee@users.noreply.github.com>
Troubleshooting activecampaign tag not firing from the head.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Adjusted analytics/tracking script so it's emitted only in production
and moved to load near the end of the page, reducing impact on initial
page rendering; the script is omitted in non-production environments.
<!-- 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#41568
# 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
<img width="751" height="239" alt="Localhost pixel 9pro xl emulation"
src="https://github.com/user-attachments/assets/2ad2d07a-aca8-4c4f-bbe2-0700736a51a4"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Style**
* Adjusted the enrollment page responsive layout so the enroll button
renders at full width on larger-resolution mobile and medium screens.
* Increased spacing between device instructions and the enrollment
action to improve layout clarity.
* Visual-only change — no functional or behavioral modifications to the
enrollment flow.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated email domain restrictions across multiple submission and
validation processes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Changes:
- Added new fields to forms that deliver emails
- Updated the actions called by those forms to return a success response
if the new input is provided
- removed two routes for unused actions
- Removed the button from the deal registration success state
**Related issue:** Resolves#42290
# 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] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Failed Windows MDM wipe attempts now create a tracked "Failed wipe"
activity showing the affected host and display name for visibility; UI
filter and activity feed now surface this type.
* **Bug Fixes**
* Improved detection and reporting of wipe result statuses so real
failures are reliably surfaced.
* Duplicate failure responses are suppressed to avoid repeated alerts.
* **Tests**
* Added tests validating wipe-failure activity creation and related
control flows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Marko Lisica <83164494+marko-lisica@users.noreply.github.com>
## Summary
- Adds Part 3 of the 3-part article series on modern endpoint management
by Ashish Kuthiala (CMO)
- Covers AI assistants for translating intent to code, and event-driven
automation for onboarding, offboarding, compliance drift, and CVE
remediation
- Follows the same format and conventions as the existing Part 1
(`the-confidence-gap.md`) and Part 2
(`rethinking-endpoint-management.md`) articles
- Publish date: 2025-07-22
Built for [Ashish
Kuthiala](https://fleetdm.slack.com/archives/D0AG9JQ53GA/p1776892978826289)
by [Kilo for Slack](https://kilo.ai/features/slack-integration)
---------
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
Co-authored-by: Ashish Kuthiala <53918208+akuthiala@users.noreply.github.com>
Unreleased bug
- Added team scoping to LocURI protection for profile deletion.
- Addressed stale MDM profile rows when hosts transfer between teams.
- Improved test coverage for Windows MDM profile deletion scenarios.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#33418
# Checklist for submitter
## 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
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**
* Fixed Windows MDM profile deletion so devices moved between teams are
correctly cleaned up and receive removal commands, preventing stale
profile configurations from persisting.
* Added a regression test ensuring deletion of no-team profiles isn’t
blocked by leftover host-profile rows or by profiles in other teams.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Automated ingestion of latest Fleet-maintained app data.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated maintained applications database with latest versions: Google
Chrome 147.0.7727.117, Ollama 0.21.1, Teleport Connect 18.7.5, Teleport
Suite 18.7.5, Webex 46.4.0.34752, and Zed 0.233.5.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: mostlikelee <16102903+mostlikelee@users.noreply.github.com>
An additional case spotted on iPhones like the 89 error code shown on
Mac.
That we want to see as a valid profile removal
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CommandUUID</key>
<string>0c53ebff-93cf-4599-853c-db6b582ff929</string>
<key>ErrorChain</key>
<array>
<dict>
<key>ErrorCode</key>
<integer>12075</integer>
<key>ErrorDomain</key>
<string>MDMErrorDomain</string>
<key>LocalizedDescription</key>
<string>The profile âFleet.WiFiâ is not installed.</string>
<key>USEnglishDescription</key>
<string>The profile âFleet.WiFiâ is not installed.</string>
</dict>
</array>
<key>Status</key>
<string>Error</string>
<key>UDID</key>
<string>REDACTED</string>
</dict>
</plist>
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved detection of "profile not found" cases in Apple MDM by
recognizing an additional error signature, reducing missed detections.
* **Tests**
* Added unit tests covering the new signature, negative cases, and mixed
error chains to ensure reliable behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#43978
# 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
For unreleased bug fixes in a release candidate, one of:
- [x] Confirmed that the fix is not expected to adversely impact load
test 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
* **Chores**
* Optimized database performance for the app synchronization process by
adding an index to improve query efficiency and reduce CPU load on large
deployments.
<!-- 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#40322 (Second part)
# 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**
* Cleans up orphaned pending Apple MDM profile removals so pending
remove rows no longer get stuck during batch processing.
* Fixes DDM status handling so remove operations are skipped/handled
based on the incoming update, preventing incorrect flips.
* Reconciler now self-heals stuck pending removal profiles across
batches.
* **Tests**
* Added unit tests covering cross-batch cleanup, orphaned pending
removes, and DDM status/reporting edge cases.
* **Documentation**
* Updated changelog entry for DDM pending/remove fixes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
<!--- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#38348
## What this PR does
On the Policies create and edit pages, the "Save" button was getting
disabled whenever Fleet's SQL parser flagged the query as having a
syntax error. That was a problem because Fleet's parser has gaps --
plenty of valid osquery SQL gets flagged as "invalid", which blocked
admins from saving perfectly good custom queries.
This PR changes the Save button behavior on the Policies form to match
what Reports (formerly "Queries") already does: we still show the
"Syntax error. Please review before saving." message under the editor,
but the user can still click Save. An empty query still disables Save.
The actual code change is small -- one line in `PolicyForm.tsx`:
```diff
- !!size(errors);
+ (!!errors.query && errors.query === EMPTY_QUERY_ERR);
```
Previously the button was disabled for any error (including syntax
errors). Now it's only disabled when the error is specifically the
empty-query error. This exactly mirrors the existing logic in
`EditQueryForm.tsx` for Reports.
I also imported `EMPTY_QUERY_ERR` from the shared validator and dropped
an obsolete `// @ts-ignore` on that import (the validator is now
TypeScript).
## Testing
All testing was done manually on macOS against a local Fleet dev server.
Jest suites run clean.
### Before the change (reproducing the bug)
1. Checked out `main`, ran the dev server.
2. Went to Policies, clicked "Add policy".
3. Pasted a query with a syntax error: `SELCT * FROM users;`.
4. Observed: error message "Syntax error. Please review before saving."
appears under the editor, and the Save button is **disabled** (greyed
out, not clickable). Same behavior when editing an existing policy.
### After the change (fix verified)
1. Checked out `bug_38348`, refreshed the browser (webpack watch picked
up the change).
2. Went to Policies, clicked "Add policy".
3. Pasted the same syntax-error query `SELCT * FROM users;`.
4. Observed: error message still shows, but the Save button is now
**enabled**. Clicking Save opened the "Save policy" modal; completing
the save wrote the policy with the user's exact SQL.
5. Edited the saved policy -- the same syntax-error SQL loaded, Save
remained enabled, edits saved successfully.
6. Cleared the SQL to empty -- error changed to "Query text must be
present" and Save went back to disabled. Good.
7. Ran the same flow on Reports (new and edit) to confirm no regression
-- behavior unchanged from before.
### Tests
-
`frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tests.tsx`
-- 17/17 passing.
-
`frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tests.tsx`
-- 15/15 passing (regression check).
- No new unit test was added for the syntax-error path; an existing TODO
in the test file documents why direct testing through react-ace is
awkward.
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`
(`changes/38348-allow-saving-invalid-sql`).
- [x] Input data is properly validated (N/A -- frontend-only, no new
SQL).
- [x] QA'd all new/changed functionality manually (see Testing section
above).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Policy forms now allow saving when the SQL has a syntax error; the
syntax-error message remains visible for correction.
* Saving is still blocked when the SQL/query is empty or only
whitespace.
* **Tests**
* Added regression tests verifying save behavior for empty and
syntactically invalid queries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Juan Fernandez <juan@fleetdm.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#43738
# 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
- Before the fix, switching from custom package to FMA via GitOps
created two software installer rows and duplicate setup experience
installers (the setup experience page said "2 software items will be
installed during setup" even though only one was selected.
- After the fix, switching from custom package to FMA via GitOps deleted
the old installer and left only one row with the correct FMA. In setup
experience, only one instance of the software was installed.
- Added a custom package (obsidian) and a policy with a software install
automation for it, then applied gitops and replaced obsidian with the
FMA version and the policy with the FMA slug, and it redirected the
policy to the new installer.
- Adding setup experience software will only set
`install_during_setup=1` on the active FMA, and not on installer rows
with `is_active=0`
<img width="1222" height="558" alt="image"
src="https://github.com/user-attachments/assets/ace5922a-63ec-4591-b615-1a8534a70805"
/>
<img width="1173" height="483" alt="image"
src="https://github.com/user-attachments/assets/05c7c718-4f4a-4549-bbf1-1e1d6dae75d0"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevent duplicate installs by ensuring only active installers are
considered during setup; remove or replace custom installers when a
managed (fleet‑maintained) installer is added, repointing policies to
the active installer and canceling now-obsolete pending setup actions.
* **Tests**
* Added tests covering active-installer selection, custom→managed
installer replacement, policy repointing, display-name preservation, and
cancellation of pending setup activities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Adds two marketing labels to the handbook for Marketing and also
includes the :mktg-ritual label to the automation
Closes: https://github.com/fleetdm/confidential/issues/15424
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated labeling configuration for marketing rituals to include an
additional organizational label, improving task categorization and
filtering capabilities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->