Skip to content

Fix missing GitOps label validation for invalid field combinations#44410

Merged
sharon-fdm merged 19 commits intomainfrom
34229
May 5, 2026
Merged

Fix missing GitOps label validation for invalid field combinations#44410
sharon-fdm merged 19 commits intomainfrom
34229

Conversation

@sharon-fdm
Copy link
Copy Markdown
Collaborator

@sharon-fdm sharon-fdm commented Apr 29, 2026

Related issue: Closes #34229

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See 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.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

fleetctl gitops silently accepted labels with invalid parameter combinations (e.g. manual labels with query/criteria/platform). Added per-type field validation in a centralized fleet.ValidateLabelMembershipFields function, called from the GitOps parser, ApplyLabelSpecs, and NewLabel.

Type Allowed Now rejects
manual name, description, hosts query, criteria, platform
dynamic name, description, query, platform criteria, hosts; validates platform value
host_vitals name, description, criteria query, platform, hosts

Automated tests

  • TestLabelInvalidFieldCombinations in pkg/spec/gitops_test.go — 17 sub-tests covering every invalid combination per label type, plus 3 valid happy-path cases.
  • TestNewLabelFieldValidation in server/service/labels_test.go — 4 cases for NewLabel validation.
  • TestApplyLabelSpecsManualLabelNilHosts — 10 sub-cases for ApplyLabelSpecs field validation.
  • TestWhenCreatingNewLabelsPlatformIsValidated — platform validation across NewLabel and ApplyLabelSpecs.

All existing pkg/spec and server/service label tests pass.

Summary by CodeRabbit

  • Bug Fixes
    • Labels now reject invalid field combinations for manual, dynamic, and host_vitals types with clear error responses instead of failing silently.
  • Tests
    • Added comprehensive tests covering valid and invalid label configurations across membership types.
  • Documentation
    • Changelog entry describing the behavioral fix.
  • Chores
    • Removed an unnecessary platform constraint from a label configuration.

Manual test results

Ran against a local Fleet server with the built binary.

API - NewLabel (POST /api/latest/fleet/labels)

Test Input Expected Result
1 manual + platform=darwin 422, field=platform PASS
2 dynamic + platform=invalidplatform 422, field=platform PASS
3 dynamic + platform=darwin + query 200 PASS
4 manual (no platform) 200 PASS
5 host_vitals + platform=darwin 422, field=platform PASS
6 dynamic + whitespace-only query 422, field=query PASS

API - ApplyLabelSpecs (POST /api/latest/fleet/spec/labels)

Test Input Expected Result
7 manual + query 422, field=query PASS
8 dynamic + hosts 422, field=hosts PASS
9 valid dynamic 200 PASS

Round-trip: get labels --yaml then apply

Test Scenario Result
10 Legacy manual label with platform=darwin in DB Platform stripped from YAML, re-apply succeeds — PASS
11 Dynamic label with platform=darwin Platform preserved in YAML, re-apply succeeds — PASS

GitOps parser (fleetctl gitops --dry-run)

Test Input Result
12 manual + query + platform + criteria All 3 errors surfaced at once — PASS
13 valid manual label No validation errors — PASS
14 dynamic + invalid platform Error surfaced — PASS

Code walkthrough

server/fleet/labels.go — Added ValidateLabelMembershipFields(*LabelSpec) *InvalidArgumentError. This is the single source of truth for label field validation, returning field-specific errors (platform, query, criteria, hosts). Lives here because this package defines the label types both callers import. Also uses strings.TrimSpace to reject whitespace-only queries.

server/service/labels.go — Three changes: (1) Removed the early blanket platform check from NewLabel that ran before the membership type was known. (2) Added ValidateLabelMembershipFields call in NewLabel after type inference, so the API rejects invalid combos at creation time. (3) Replaced three incomplete inline checks in ApplyLabelSpecs with a single call to the centralized function, using err.WithStatus(422) to preserve field-specific error shape in the API response.

pkg/spec/gitops.go — Replaced the inline validation switch and a standalone ValidLabelPlatformVariants check with a call to ValidateLabelMembershipFields. Unwraps the returned errors individually into multiError so all validation problems are reported to the user at once.

cmd/fleetctl/fleetctl/generate_gitops.go — Gated platform emission on LabelMembershipTypeDynamic so legacy manual/host_vitals labels with a stored platform don't produce YAML that fails re-import.

cmd/fleetctl/fleetctl/get.go — Added stripMismatchedLabelFields which clears type-inappropriate fields (query, platform, criteria, hosts) per membership type before YAML output. Called in both code paths: listing all labels and fetching a single label by name. Ensures the get labels --yamlapply round-trip works for legacy data.

server/datastore/mysql/labels.go — Added missing l.criteria column to GetLabelSpec SELECT, matching GetLabelSpecs. Without it, host_vitals labels fetched by name lost their criteria in the YAML output, causing re-import to fail with the new validation.

GitOps silently accepted labels with invalid parameter combinations
(e.g. manual labels with query/criteria/platform). Add per-type
validation in both the GitOps parser and the service layer.

Closes #34229
@sharon-fdm sharon-fdm marked this pull request as ready for review April 29, 2026 18:51
@sharon-fdm sharon-fdm requested a review from a team as a code owner April 29, 2026 18:51
Copilot AI review requested due to automatic review settings April 29, 2026 18:51
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds centralized validation for label membership fields via a new exported function fleet.ValidateLabelMembershipFields(*LabelSpec) []error, calls that validator from gitops label parsing and service endpoints (NewLabel, ApplyLabelSpecs), updates fleetctl gitops output to emit platform only for dynamic labels, adds tests for invalid field combinations, and removes a platform value from one label YAML. Validation errors are now accumulated and returned instead of being silently ignored.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: adding validation for invalid GitOps label field combinations, which directly matches the core change across all modified files.
Linked Issues check ✅ Passed All four coding requirements from issue #34229 are fully met: invalid criteria combinations rejected, query/platform rejected for manual/host_vitals, platform values validated, and clear error messages provided with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are in-scope: validation logic implementation, test additions, YAML configuration fix for dogfood compatibility, and fleetctl generation adjustment for consistency with validation rules.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the issue, changes made, validation rules, testing approach, and manual QA results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 34229

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/service/labels_test.go (1)

771-860: Add a missing negative test: dynamic label without query.

This block covers many invalid combinations, but it doesn’t assert that a dynamic label with empty Query is rejected. Adding that case will lock in expected service behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/labels_test.go` around lines 771 - 860, The tests miss a
negative case asserting that a dynamic label with an empty or missing Query is
rejected; add a test call to svc.ApplyLabelSpecs passing a fleet.LabelSpec with
Name like "dynamic_no_query", LabelMembershipType:
fleet.LabelMembershipTypeDynamic and no Query (or empty string) and assert an
error containing a message like "declared as dynamic but contains no query" to
mirror the other validations; locate the test near the other invalid-combination
checks in labels_test.go and use the same pattern (require.Error and
require.ErrorContains) as the surrounding cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/service/labels.go`:
- Around line 590-600: In the fleet.LabelMembershipTypeDynamic branch (the
switch case that checks fleet.LabelMembershipTypeDynamic), add a validation that
rejects empty or nil spec.Query in the same way you already reject
spec.HostVitalsCriteria and spec.Hosts: return
fleet.NewUserMessageError(ctxerr.Errorf(ctx, "label %s is declared as dynamic
but missing query", spec.Name), http.StatusUnprocessableEntity). Ensure this
check runs alongside the existing checks for spec.HostVitalsCriteria and
len(spec.Hosts) so dynamic labels must provide a non-empty Query.

---

Nitpick comments:
In `@server/service/labels_test.go`:
- Around line 771-860: The tests miss a negative case asserting that a dynamic
label with an empty or missing Query is rejected; add a test call to
svc.ApplyLabelSpecs passing a fleet.LabelSpec with Name like "dynamic_no_query",
LabelMembershipType: fleet.LabelMembershipTypeDynamic and no Query (or empty
string) and assert an error containing a message like "declared as dynamic but
contains no query" to mirror the other validations; locate the test near the
other invalid-combination checks in labels_test.go and use the same pattern
(require.Error and require.ErrorContains) as the surrounding cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96c1a917-29e9-4ec0-83b6-51fc58ac552e

📥 Commits

Reviewing files that changed from the base of the PR and between 3270c9f and cbc195b.

📒 Files selected for processing (5)
  • changes/34229-gitops-label-validation
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/service/labels.go
  • server/service/labels_test.go

Comment thread server/service/labels.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes fleetctl gitops label validation so invalid field combinations (e.g. manual labels specifying query/criteria/platform) are rejected with clear errors, addressing #34229.

Changes:

  • Added per-label-type validation in the GitOps label parser (pkg/spec/gitops.go) for mutually exclusive/required fields and dynamic platform value validation.
  • Added service-layer validation for invalid label field combinations in ApplyLabelSpecs (server/service/labels.go).
  • Expanded automated test coverage for invalid/valid label specs in both GitOps parsing and service application.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/labels.go Adds service-layer validation of mutually exclusive fields per label membership type.
server/service/labels_test.go Adds service tests ensuring invalid label spec combinations are rejected.
pkg/spec/gitops.go Adds GitOps parser validation for per-type field rules and dynamic platform validation.
pkg/spec/gitops_test.go Adds GitOps tests covering invalid field combinations and valid cases.
changes/34229-gitops-label-validation User-visible changelog entry for the validation fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/service/labels.go Outdated
Comment thread server/service/labels.go
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 87.09677% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.69%. Comparing base (bf9f5b9) to head (41fe96d).
⚠️ Report is 53 commits behind head on main.

Files with missing lines Patch % Lines
cmd/fleetctl/fleetctl/get.go 46.66% 8 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #44410   +/-   ##
=======================================
  Coverage   66.68%   66.69%           
=======================================
  Files        2651     2651           
  Lines      213525   213609   +84     
  Branches     9768     9767    -1     
=======================================
+ Hits       142392   142466   +74     
- Misses      58167    58180   +13     
+ Partials    12966    12963    -3     
Flag Coverage Δ
backend 68.56% <87.09%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sharon-fdm
Copy link
Copy Markdown
Collaborator Author

@claude review

…ation into switch

- Dynamic case now rejects empty query (matches GitOps parser)
- Platform validation moved inside dynamic case only (manual/host_vitals
  already reject any platform, so the top-level check was misleading)
- Added test for dynamic label missing query
# Conflicts:
#	server/service/labels_test.go
Legacy manual/host_vitals labels may have a non-empty platform in the
DB. generate-gitops would include it in the YAML, breaking the
round-trip since the new validation rejects platform on those types.
Comment thread pkg/spec/gitops.go
# Conflicts:
#	it-and-security/lib/all/labels/nudge-test-devices.yml
@allenhouchins allenhouchins removed their request for review May 4, 2026 18:23
Comment thread server/service/labels.go
Comment thread server/service/labels.go
Address review: ValidateLabelMembershipFields now returns
*InvalidArgumentError with per-field entries (platform, query,
criteria, hosts) instead of generic errors.

Also removes the leftover broad platform check in gitops.go and
strips platform from non-dynamic labels in fleetctl get labels output
to fix the get→apply round-trip for legacy data.
Comment thread server/service/labels.go Outdated
Use err.WithStatus(422) instead of wrapping in UserMessageError, so
the encoder preserves field-specific entries matching NewLabel behavior.
Comment thread cmd/fleetctl/fleetctl/get.go
Strip Query/HostVitalsCriteria/Platform/Hosts per membership type in
fleetctl get labels output so legacy rows with type-mismatched fields
survive the dump-and-reapply round-trip.

Also add missing criteria column to ds.GetLabelSpec SELECT to match
ds.GetLabelSpecs, fixing host_vitals labels fetched by name.
@sharon-fdm
Copy link
Copy Markdown
Collaborator Author

Review Comments Addressed

# Reviewer Comment Fix Code
1 nulmete Centralize duplicated validation into one function Created fleet.ValidateLabelMembershipFields returning *InvalidArgumentError with per-field entries function
2 nulmete Add same validation to Create endpoint Added call in NewLabel after type inference NewLabel
3 nulmete Dogfood nudge-test-devices.yml has manual+platform Removed platform: darwin; file later deleted on main deleted on main
4 nulmete Return field-specific errors, not generic "label" ValidateLabelMembershipFields returns *InvalidArgumentError with field names (platform, query, criteria, hosts) same as #1
5 claude[bot] Dogfood pipeline will break Same as #3
6 claude[bot] NewLabel round-trip breakage Same as #2
7 claude[bot] Wording mismatch: "is missing criteria" vs "contains no criteria" Fixed in prior commit deb32db
8 claude[bot] generate-gitops emits platform for manual/host_vitals labels Gated emission on LabelMembershipTypeDynamic generate_gitops.go
9 claude[bot] get labels --yamlapply broken for legacy manual labels with platform Strip all mismatched fields per type via stripMismatchedLabelFields get.go
10 claude[bot] Leftover broad platform check in gitops.go stacks duplicate errors Removed the old ValidLabelPlatformVariants check gitops.go
11 claude[bot] ApplyLabelSpecs wraps error losing field-specific shape Return err.WithStatus(422) directly, matching NewLabel ApplyLabelSpecs
12 claude[bot] printLabel only strips platform, not Query/Criteria; GetLabelSpec missing criteria column Added per-type field stripping and l.criteria to GetLabelSpec SELECT stripMismatchedLabelFields, GetLabelSpec
13 Copilot Dynamic label with empty query passes validation Fixed in prior commit b6cd571
14 Copilot Platform validation runs before membership-type check Moot — centralized function handles per-type function
15 coderabbitai Dynamic labels without query pass service validation Same as #13
16 coderabbitai Whitespace-only query bypasses "missing query" check Using strings.TrimSpace line
17 coderabbitai NewLabel errors use generic "label" field name Same as #4

All 17 review threads resolved.

Copy link
Copy Markdown
Member

@nulmete nulmete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Tested a few scenarios below:

  • gitops surfaces multiple errors when there's an invalid combination
Image
  • created a manual label with platform="windows" (AFAICS we could have some records in prod with this configuration) - then verified that getting the YAML for it doesn't include the platform
Image Image
  • applied gitops with a valid manual label and verified it shows up on the UI
Image Image

NOTE: I see we're referencing "teams" instead of "fleets" so I'll create a separate issue for that.

@sharon-fdm sharon-fdm merged commit 1c52209 into main May 5, 2026
60 checks passed
@sharon-fdm sharon-fdm deleted the 34229 branch May 5, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

We do not show the expected errors when adding labels via gitops with invalid combinations of parameters

3 participants