Allow glob literals in filenames (#44547)

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

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
- [X] added a script file `some-*-script[].sh` and referred to it in a
gitops file using `path:`. Failed on main; on this branch it
successfully uploaded the script
- [X] still got expected error message when using `path: ` with a value
that had glob characters that _didn't_ match an actual file
- [X] `paths:` still worked and uploaded multiple files, including
`some-*-script[].sh`


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

* **Bug Fixes**
* Fixed path validation in fleetctl gitops so path values containing
glob metacharacters (e.g., brackets, asterisks, question marks) are
accepted when a literal file with that name exists on disk; missing
files still produce the appropriate error.

* **Tests**
* Added regression tests covering glob metacharacter handling in path
validation.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44547)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Scott Gress
2026-05-11 13:49:31 -05:00
committed by GitHub
parent c7364d555c
commit fe16654729
3 changed files with 50 additions and 4 deletions
@@ -0,0 +1 @@
- Fixed an issue where `fleetctl gitops` rejected `path:` values whose actual filenames contained glob metacharacters even when the file existed at that literal path.
+14 -4
View File
@@ -1297,11 +1297,21 @@ func expandBaseItems[T any, PT interface {
result = append(result, entity)
// Single path -- resolve to absolute path and add to result.
case hasPath:
if containsGlobMeta(*baseItem.Path) {
errs = append(errs, fmt.Errorf(`%s "path" %q contains glob characters; use "paths" for glob patterns`, entityType, *baseItem.Path))
continue
}
resolved := resolveApplyRelativePath(baseDir, *baseItem.Path)
// Reject glob metacharacters in "path:" only when the literal path
// does not resolve to an existing file. This allows filenames that
// contain literal glob metacharacters (e.g. Windows CSP names like
// "[AllowSpotlightCollection].xml") to be referenced via "path:".
if containsGlobMeta(*baseItem.Path) {
if _, err := os.Stat(resolved); err != nil {
if os.IsNotExist(err) {
errs = append(errs, fmt.Errorf(`%s "path" %q contains glob characters; use "paths" for glob patterns`, entityType, *baseItem.Path))
} else {
errs = append(errs, fmt.Errorf("failed to stat %s path %q: %w", entityType, resolved, err))
}
continue
}
}
// Check for duplicate filenames if requested.
if opts.RequireUniqueBasenames {
base := filepath.Base(resolved)
+35
View File
@@ -2217,6 +2217,41 @@ func TestExpandBaseItems(t *testing.T) {
requireErrorContains(t, errs, `contains glob characters`)
})
// Filenames containing glob metacharacters should be accepted by "path:"
// when the literal file exists on disk. Common with Windows MDM CSP
// profile names like "[AllowSpotlightCollection].xml". Regression test for
// fleetdm/fleet#43598.
t.Run("path_with_literal_glob_meta_chars_existing_file", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
filenames := []string{
"AllowRebootless -[Updates].xml",
"profile{a,b}.xml",
}
for _, name := range filenames {
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(""), 0o644))
}
items := make([]fleet.BaseItem, 0, len(filenames))
for _, name := range filenames {
items = append(items, fleet.BaseItem{Path: ptr.String(name)}) //nolint:modernize
}
result, errs := expandBaseItems(items, dir, "test", GlobExpandOptions{})
require.Empty(t, errs)
require.Len(t, result, len(filenames))
for i, name := range filenames {
assert.Equal(t, filepath.Join(dir, name), *result[i].Path)
}
})
t.Run("path_with_glob_meta_chars_missing_file_error", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
items := []fleet.BaseItem{{Path: ptr.String("does-not-[exist].xml")}} //nolint:modernize
_, errs := expandBaseItems(items, dir, "test", GlobExpandOptions{})
requireErrorContains(t, errs, `contains glob characters`)
})
t.Run("both_path_and_paths_error", func(t *testing.T) {
t.Parallel()
items := []fleet.BaseItem{{Path: ptr.String("foo.yml"), Paths: ptr.String("*.yml")}} //nolint:modernize