Fixes#42885
Added new middleware (APIOnlyEndpointCheck) that enforces 403 for
API-only users whose request either isn't in the API endpoint catalog or
falls outside their configured per-user endpoint restrictions.
Resolves#40396.
No changes file because there should be no user visible changes.
## Testing
- [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] Verified that fleetd runs on macOS, Linux and Windows
- [X] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#38889
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Updated internal logging infrastructure to improve standardization and
maintainability. Logging functionality remains unchanged from an
end-user perspective.
<!-- 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#38607
Contributor docs update:
https://github.com/fleetdm/fleet/pull/39285/changes
Another contributor docs update:
https://github.com/fleetdm/fleet/pull/39402/changes
Also:
- renamed OtelHandler to OtelTracingHandler
- made "opentelemetry" be the default when tracing is enabled
- updated OTEL dependencies
# 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
## New Fleet configuration settings
- [x] Setting(s) is/are explicitly excluded from GitOps
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added OpenTelemetry log export capability, enabling logs to be sent to
OpenTelemetry collectors.
* New configuration option `logging.otel_logs_enabled` (requires tracing
to be enabled).
* **Chores**
* Updated OpenTelemetry dependencies to v1.40.0 with latest OTLP
exporters and logging support.
* Updated dependencies including gRPC (v1.78.0), Google libraries, and
cryptography packages.
<!-- 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#37192
- Move /server/service/middleware/endpoint_utils to
/server/platform/endpointer
- Move /server/service/middleware/authzcheck to
/server/platform/middleware/authzcheck
- Move /server/service/middleware/ratelimit to
/server/platform/middleware/ratelimit
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Refactor**
* Reorganized internal endpoint utilities to a centralized platform
location for improved code organization and maintainability. No
functional changes to existing features or APIs.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolves#37192
Separating generic endpoint_utils middleware logic from domain-specific
business logic. New bounded contexts would share the generic logic and
implement their own domain-specific logic. The two approaches used in
this PR are:
- Use common `platform` types
- Use interfaces
In the next PR we will move `endpointer_utils`, `authzcheck` and
`ratelimit` into `platform` directory.
# Checklist for submitter
- [x] Added changes file
## Testing
- [x] Added/updated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Restructured internal error handling and context management to support
bounded context architecture.
* Improved error context collection and telemetry observability through
a provider-based mechanism.
* Decoupled licensing and authentication concerns into interfaces for
better modularity.
* **Chores**
* Updated internal package dependencies to align with new architectural
boundaries.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- 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#36670
Refactoring `middleware/endpoint_utils` package to remove direct
dependencies on:
- fleet.Service
- android.Service
Specific changes are:
- replace AuthFunc+FleetService with AuthMiddleware
- Move the definition of handler functions to the respective services
and use a generic `CommonEndpointer[H any] struct`
Although this was discovered as part of Activity bounded context
research, this change is not directly related to bounded contexts.
In retrospect, this decoupling should have been done when creating the
Android service for ADR-0001.
## Testing
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Internal restructuring of endpoint handling and authentication
middleware composition to improve code maintainability and type safety.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit fixes two related bugs with Android MDM:
1. Android profiles now download correctly as .json files instead of
.xml
- Before: profiles downloaded as .xml with content '[object Object]'
- After: profiles download as .json with properly formatted JSON content
- Fixed by adding Android platform check in createProfileExtension() and
createFileContent()
2. Custom Settings page now recognizes Android MDM
- Before: showed 'MDM must be turned on' error even when Android MDM was
enabled
- After: properly detects Android MDM and allows profile management
- Fixed by adding android_enabled_and_configured check to mdmEnabled
- Backend middleware now supports Android MDM for profile endpoints
a) Added VerifyAnyMDMConfigured() to support Apple, Windows, and Android
MDM
b) Updated profile endpoints to use VerifyAnyMDM() middleware
**Related issue:** Resolves#35023
# 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)
- [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
## Database migrations
_No database migrations in this PR_
## New Fleet configuration settings
_No new Fleet configuration settings in this PR_
## fleetd/orbit/Fleet Desktop
_This PR does not affect fleetd/orbit/Fleet Desktop_
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#34539
Added endpoint to get a sample Apple config profile that IT admin can
use for Okta conditional access configuration.
`/api/_version_/fleet/conditional_access/idp/apple/profile`
And additional cleanup/improvements:
- logging
- error handling (sending errors to Sentry/OTEL)
- redirect end user to error page if IT admin hasn't set up conditional
access in Fleet
Contributor API changes at:
https://github.com/fleetdm/fleet/pull/35632/files
# Checklist for submitter
- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- Will be added to related PR: #35204
## 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 Apple profile generation for Okta conditional access IdP
integration.
* New endpoint for retrieving conditional access Apple configuration
profiles.
* **Bug Fixes**
* Improved error handling and logging for conditional access operations.
* Enhanced error responses for missing server URL configuration.
* **Refactoring**
* Centralized error handling for internal server errors with improved
context logging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolves#31890
This new approach allows up to 1000 consecutive failing requests per
minute.
If the threshold of 1000 consecutive failures is reached for an IP, then
we ban request (return 429) from such IP for a duration of 1 minute.
(Any successful request for an IP clears the count.)
This supports the scenario where all hosts are behind a NAT (same IP)
AND still provides protection against brute force attacks (attackers can
only probe 1k requests per minute).
This approach was discussed in Slack with @rfairburn:
https://fleetdm.slack.com/archives/C051QJU3D0V/p1755625131298319?thread_ts=1755101701.844249&cid=C051QJU3D0V.
- [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] 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
- Introduced IP-based rate limiting for Fleet Desktop endpoints to
better support many hosts behind a single public IP (NAT). Requests from
abusive IPs may be temporarily blocked, returning 429 Too Many Requests
with a retry-after hint.
- Documentation
- Added README for a new desktop rate-limit tester, describing usage and
expected behavior.
- Tests
- Added integration tests covering desktop endpoint rate limiting and
Redis-backed banning logic.
- Chores
- Added a command-line tool to stress-test desktop endpoints and verify
rate limiting behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
For #32788.
## 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
- [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
- Enhanced Linux setup experience: persists status on disk, resumes
automatically, and completes when software/scripts finish.
- Opens the “My Device” page only when desktop is enabled, using a
user-aware launcher on Linux.
- Linux setup status now focuses on software progress for faster,
clearer feedback.
- Bug Fixes
- Corrected auth/MDM checks: macOS requires Apple MDM; Linux no longer
blocked by MDM configuration on shared endpoints.
- Improved reliability and logging around software installation and
temporary directory cleanup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes#32331
Manually tested all paths. `/test` path removed in
https://github.com/fleetdm/fleet/pull/32962
Also added support for sending errors to OpenTelemetry, like we do for
APM/Sentry.
# 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] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added OpenTelemetry tracing across core HTTP endpoints (health,
version, assets, metrics, enroll/root, debug, Apple MDM, SCEP, SCIM)
with dynamic per-request route instrumentation.
* Enhanced error reporting to include OpenTelemetry spans/events with
contextual user/host attributes.
* **Tests**
* Added unit tests validating SCIM and error-handling telemetry, span
naming, and sensitive-data redaction.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
#31592
There's still some QA to be done for edge cases and re-connects, but
this is ready for review.
<img width="341" height="103" alt="Screenshot 2025-08-07 at 11 19 33 AM"
src="https://github.com/user-attachments/assets/01e48ca2-8ab1-412c-be01-8e806a5a8b1c"
/>
Changes:
- To improve UX I'm now using `HEAD /api/fleet/device/ping` API every 10
seconds for connectivity/offline check (instead of the expensive
DesktopSummary one every 5 minutes). This is to address feedback from a
customer:
> "If the internet is not connected and we reconnect with an ethernet
connection for example, it would be good to try to see if we can refresh
it text from the offline indicator given that's not the case anymore.
- It might take up to 1m for Fleet Desktop to show the offline indicator
(we check every 10s with ping and now we are adding 6 more requests in 1
minute to make sure just one bad request doesn't unnecessarily display
the offline indicator).
- Requests without proper public IP were being incorrectly rate limited
(all under the same bucket). So we will now not make these requests and
instead log a WARNING. This is a-ok as the recommended approach to
deploy Fleet is with a TLS terminator that will add the public IP of the
request before sending it to Fleet.
---
- [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
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [ ] QA'd all new/changed functionality manually
## fleetd/orbit/Fleet Desktop
- [ ] 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))
- [ ] Verified that fleetd runs on macOS, Linux and Windows
- [ ] 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
* **Bug Fixes**
* Improved accuracy in identifying client public IP addresses, reducing
incorrect rate limiting for Fleet Desktop users.
* Offline indicator is now less sensitive to brief network
interruptions, reducing false offline signals and allowing faster
recovery when connectivity is restored.
* Updated offline message for clearer status communication.
* **New Features**
* Enhanced error messages and logging for rate limiting events,
providing clearer feedback when limits are reached.
* **Tests**
* Expanded test coverage for rate limiting, including scenarios with
missing public IPs and improved assertions for error handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
For https://github.com/fleetdm/confidential/issues/9931.
[Here](ec3e8edbdc/docs/Contributing/Testing-and-local-development.md (L339))'s
how to test SAML locally with SimpleSAML.
- [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/Committing-Changes.md#changes-files)
for more information.
- [x] Added/updated automated tests
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [x] Manual QA for all new/changed functionality
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved SSO and SAML integration with enhanced session management
using secure cookies.
* Added support for IdP-initiated login flows.
* Introduced new tests covering SSO login flows, metadata handling, and
error scenarios.
* **Bug Fixes**
* Enhanced validation and error handling for invalid or tampered SAML
responses.
* Fixed session cookie handling during SSO and Apple MDM SSO flows.
* **Refactor**
* Replaced custom SAML implementation with the crewjam/saml library for
improved reliability.
* Simplified SAML metadata parsing and session store management.
* Streamlined SSO authorization request and response processing.
* Removed deprecated fields and redundant code related to SSO.
* **Documentation**
* Updated testing and local development docs with clearer instructions
for SSO and IdP-initiated login.
* **Chores**
* Upgraded dependencies including crewjam/saml and related packages.
* Cleaned up tests and configuration by removing deprecated fields and
unused imports.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Also adds some more rate limiter tests to make sure separate rate limit
buckets interact as expected.
Fixes#29614.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
<!-- Note that API documentation changes are now addressed by the
product design team. -->
- [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)
- For new Fleet configuration settings
- [x] Verified that the setting can be managed via GitOps, or confirmed
that the setting is explicitly being excluded from GitOps. (excluded;
env var or YAML)
- [x] Added/updated automated tests
- [ ] Manual QA for all new/changed functionality
---------
Co-authored-by: George Karr <georgekarrv@users.noreply.github.com>
Co-authored-by: Noah Talerman <47070608+noahtalerman@users.noreply.github.com>
For #27287
Video explaining the PR: https://www.youtube.com/watch?v=ZHgFUAvrPEI
This PR adds SCIM Users support for Okta. The goal is to first add
Users/Groups support so that the remaining backend SCIM work can be done
in parallel.
This PR does not include the following, which will be added in later PRs
- Changes file
- Groups support for Okta
- Full support for Entra ID
- Integration tests
# Checklist for submitter
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For 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`).
- [x] Added/updated automated tests
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [x] Manual QA for all new/changed functionality
For #27234.
- Improved logging in orbit to help us during troubleshooting.
- Added some documentation on how to grep for errors related to software
package installation in orbit.
- Added `took` to server request error logs (it was only present when
the request succeeds).
---
- [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/Committing-Changes.md#changes-files)
for more information.
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [x] Make sure fleetd is compatible with the latest released version of
Fleet (see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/fleetd-development-and-release-strategy.md)).
- [x] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [x] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [x] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
For #26218
- Added `GET /api/_version_/fleet/android_enterprise` andpoint and tests
- Set up some testing infrastructure for Android service tests -- see
new README.md
# Checklist for submitter
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
For #26219
Video explaining the PR: https://www.youtube.com/watch?v=t2hmpE7N-50
This PR includes:
- applying a default/dummy policy to Android enterprise
- Setting up PubSub topic and subscription to point at Fleet Server
- Generating device enrollment token
- Enrolling a device
Although some tests were added, this PR is mostly manually tested.
Integration tests are an outstanding task.
# Checklist for submitter
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For 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`).
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
Android scaffold code and refactorings
- Android packages intended to be decoupled from other Fleet code
Video explaining the PR: https://www.youtube.com/watch?v=cza-35Z9Wxk
# Checklist for submitter
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For 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`).
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
Refactoring some functionality out of the service package so it can be
reused by a different service package.
- auth middleware
- logging errors
No functional changes.
#16076
This change removes ineffective rate limit to `/api/fleet/device/ping`
and `api/fleet/orbit/ping`.
Currently these endpoints are not rate limited, because the rate
limiting used in these was the `errorLimiter` which only takes effect if
the request fails and the ping endpoints never fail. So... we were
making ineffective Redis accesses on every `/api/fleet/device/ping` and
`api/fleet/orbit/ping` requests (we use Redis as the limiter store).
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Manual QA for all new/changed functionality
Fixed 500 return code from several endpoints.
/api/v1/fleet/perform_required_password_reset
- Now returns 403 when Authorization token is missing
/api/v1/fleet/hosts_summary
- Now returns 400 when low_disk_space parameter is invalid
/api/v1/fleet/demologin
- Now returns 403
/api/v1/fleet/sessions/*
- Now returns 400 on invalid input
#12274
# 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/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This PR requires the Windows MDM configuration changes - This will be
updated next week
- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- [x] Documented any permissions changes
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
* Add rate limits for device authed endpoints
* Fix lint
* Add missing test
* Fix test
* Increase the quota for desktop endpoints
* Add comment about quota
This should support Redis in both cluster and non-cluster modes.
Updates were made separately to github.com/throttled/throttled to support the slight changes in types.
Co-authored-by: Joseph Macaulay <joseph.macaulay@uber.com>
Co-authored-by: Zach Wasserman <zach@fleetdm.com>