From 32669f553ab48c7d04b4ec8ef26e13b94b4fbd06 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:35 +0000 Subject: [PATCH 01/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../EditConfigurationModal.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditConfigurationModal/EditConfigurationModal.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditConfigurationModal/EditConfigurationModal.tsx index 2815e3b7fec..bd553929afe 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditConfigurationModal/EditConfigurationModal.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditConfigurationModal/EditConfigurationModal.tsx @@ -29,7 +29,7 @@ import { getDisplayedSoftwareName } from "../../helpers"; const baseClass = "edit-configuration-modal"; export interface ISoftwareConfigurationFormData { - configuration: string; + configuration: string | Record; } interface IEditConfigurationModalProps { @@ -101,13 +101,14 @@ const EditConfigurationModal = ({ // iOS/iPadOS: send XML as a string return { configuration: formData }; } - // Android: send parsed JSON object (cast to string to match interface; - // runtime value is an object that gets serialized by sendRequest) + // Android: send parsed JSON object; the interface allows either a string + // (Apple/XML) or an object (Android/JSON), matching the actual runtime + // value that gets serialized by sendRequest. if (formData === "") { - return { configuration: ({} as unknown) as string }; + return { configuration: {} }; } return { - configuration: (JSON.parse(formData) as unknown) as string, + configuration: JSON.parse(formData) as Record, }; }; @@ -303,3 +304,7 @@ const EditConfigurationModal = ({ }; export default EditConfigurationModal; +FILE>>> + +<<` so it truthfully reflects both runtime shapes (XML string for Apple, parsed JSON object for Android). Removed the lying `as unknown as string` double-casts in `buildSubmitPayload()`, now returning `{ configuration: {} }` and `{ configuration: JSON.parse(formData) as Record }` directly with no unsafe cast. This is a local-file fix; risk is that `softwareAPI.editSoftwarePackage`/`editAppStoreApp` (defined in `services/entities/software`, not visible here) may have parameter types declared strictly as `string` for the configuration field, which could now produce a type error at the call sites in this same file — I cannot see/edit that service file to confirm or widen its signature, so a complete fix may additionally require updating the corresponding type in `services/entities/software`. From 8910d32b3f1491560f8f088d9d00a56c771eb1da Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:37 +0000 Subject: [PATCH 02/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/mdm/apple/loadtest/loadtest.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/mdm/apple/loadtest/loadtest.go b/tools/mdm/apple/loadtest/loadtest.go index 566a0777754..8b7d4b8a412 100644 --- a/tools/mdm/apple/loadtest/loadtest.go +++ b/tools/mdm/apple/loadtest/loadtest.go @@ -82,6 +82,10 @@ func main() { log.Fatalf("host count (%d) must match expected team count (%d)", len(hosts), *teamCount) } + if *teamExtraCount > len(hosts) { + log.Fatalf("team_extra_count (%d) exceeds available hosts (%d)", *teamExtraCount, len(hosts)) + } + printfAndPrompt("1. Creating %d teams...", *teamCount) start := time.Now() @@ -709,3 +713,4 @@ var newProfile = []byte(` 1 `) + From 829e1629f607443d01dce32241bd65baa6de2ac3 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:38 +0000 Subject: [PATCH 03/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- cmd/fleet/cron_test.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/fleet/cron_test.go b/cmd/fleet/cron_test.go index b43dccb065b..c145aa5fc19 100644 --- a/cmd/fleet/cron_test.go +++ b/cmd/fleet/cron_test.go @@ -360,9 +360,11 @@ func TestHostVitalsLabelMembershipCronIDP(t *testing.T) { hosts := make([]*fleet.Host, 3) teamIDs := []*uint{&team1.ID, &team2.ID, nil} for i := range 3 { + osqueryHostID := fmt.Sprintf("idp-cron-%d", i) + nodeKey := fmt.Sprintf("idp-cron-%d", i) h, err := ds.NewHost(ctx, &fleet.Host{ - OsqueryHostID: new(fmt.Sprintf("idp-cron-%d", i)), - NodeKey: new(fmt.Sprintf("idp-cron-%d", i)), + OsqueryHostID: &osqueryHostID, + NodeKey: &nodeKey, UUID: fmt.Sprintf("idp-cron-uuid%d", i), Hostname: fmt.Sprintf("idp-cron-host%d.local", i), HardwareSerial: fmt.Sprintf("idp-cron-hwd%d", i), @@ -376,9 +378,10 @@ func TestHostVitalsLabelMembershipCronIDP(t *testing.T) { // All three SCIM users are in the same "Engineering" IdP group. scimUserIDs := make([]uint, 3) for i := range 3 { + active := true id, err := ds.CreateScimUser(ctx, &fleet.ScimUser{ UserName: fmt.Sprintf("idp-cron-user%d", i), - Active: new(true), + Active: &active, }) require.NoError(t, err) scimUserIDs[i] = id @@ -393,18 +396,21 @@ func TestHostVitalsLabelMembershipCronIDP(t *testing.T) { _, err = ds.CreateScimGroup(ctx, &fleet.ScimGroup{DisplayName: "Engineering", ScimUsers: scimUserIDs}) require.NoError(t, err) + vital := "end_user_idp_group" + value := "Engineering" criteria, err := json.Marshal(&fleet.HostVitalCriteria{ - Vital: new("end_user_idp_group"), - Value: new("Engineering"), + Vital: &vital, + Value: &value, }) require.NoError(t, err) // Create a global and a team1-scoped IdP host vitals label. + criteriaRawMessage := json.RawMessage(criteria) globalLabel, err := ds.NewLabel(ctx, &fleet.Label{ Name: "idp-cron-global", LabelType: fleet.LabelTypeRegular, LabelMembershipType: fleet.LabelMembershipTypeHostVitals, - HostVitalsCriteria: new(json.RawMessage(criteria)), + HostVitalsCriteria: &criteriaRawMessage, }) require.NoError(t, err) team1Label, err := ds.NewLabel(ctx, &fleet.Label{ @@ -412,7 +418,7 @@ func TestHostVitalsLabelMembershipCronIDP(t *testing.T) { TeamID: &team1.ID, LabelType: fleet.LabelTypeRegular, LabelMembershipType: fleet.LabelMembershipTypeHostVitals, - HostVitalsCriteria: new(json.RawMessage(criteria)), + HostVitalsCriteria: &criteriaRawMessage, }) require.NoError(t, err) From 43aa5af0d022a395ed66ea6035c0e81c6e30dd27 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:40 +0000 Subject: [PATCH 04/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../DeleteEntraClientIDModal/DeleteEntraClientIDModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/DeleteEntraClientIDModal/DeleteEntraClientIDModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/DeleteEntraClientIDModal/DeleteEntraClientIDModal.tsx index fb985b1de21..602568a0f2e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/DeleteEntraClientIDModal/DeleteEntraClientIDModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/DeleteEntraClientIDModal/DeleteEntraClientIDModal.tsx @@ -28,7 +28,9 @@ const DeleteEntraClientIdModal = ({ try { const currentClientIds = config?.mdm.windows_entra_client_ids ?? []; - const updatedClientIds = currentClientIds.filter((id) => id !== clientId); + const updatedClientIds = currentClientIds.filter( + (id) => id.toLowerCase() !== clientId.toLowerCase() + ); const updateData = await configAPI.update({ mdm: { windows_entra_client_ids: updatedClientIds, From c30afedd0128e3e141907ce18b41c3ad53a4509e Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:41 +0000 Subject: [PATCH 05/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- frontend/pages/ApiOnlyUser/ApiOnlyUser.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/pages/ApiOnlyUser/ApiOnlyUser.tsx b/frontend/pages/ApiOnlyUser/ApiOnlyUser.tsx index 6717a831697..8d12a2ee666 100644 --- a/frontend/pages/ApiOnlyUser/ApiOnlyUser.tsx +++ b/frontend/pages/ApiOnlyUser/ApiOnlyUser.tsx @@ -30,6 +30,7 @@ const ApiOnlyUser = ({ router }: IApiOnlyUserProps): JSX.Element => { } } catch (response) { console.error(response); + router.push(LOGIN); } }; From d1cb2e6131e52b044fbb95da5fdf23a9fb0be325 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:43 +0000 Subject: [PATCH 06/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../Users/components/UsersForm/UsersForm.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/pages/ManageControlsPage/SetupExperience/cards/Users/components/UsersForm/UsersForm.tsx b/frontend/pages/ManageControlsPage/SetupExperience/cards/Users/components/UsersForm/UsersForm.tsx index 4ecab1a6be5..8e4bf79c73e 100644 --- a/frontend/pages/ManageControlsPage/SetupExperience/cards/Users/components/UsersForm/UsersForm.tsx +++ b/frontend/pages/ManageControlsPage/SetupExperience/cards/Users/components/UsersForm/UsersForm.tsx @@ -99,8 +99,12 @@ const UsersForm = ({ e.preventDefault(); setIsUpdating(true); - const canLockEndUserInfo = - formData.endUserAuthEnabled && formData.lockEndUserInfo; + // Only collapse lockEndUserInfo based on endUserAuthEnabled when Apple + // MDM is configured. Otherwise the checkbox is read-only and reflects a + // value preserved from the backend, so it should be sent as-is. + const lockEndUserInfoToSend = isMacMdmEnabledAndConfigured + ? formData.endUserAuthEnabled && formData.lockEndUserInfo + : formData.lockEndUserInfo; try { await mdmAPI.updateSetupExperienceSettings({ @@ -108,7 +112,7 @@ const UsersForm = ({ enable_end_user_authentication: formData.endUserAuthEnabled, // Apple-only fields are omitted when Apple MDM isn't configured. ...(isMacMdmEnabledAndConfigured && { - lock_end_user_info: canLockEndUserInfo, + lock_end_user_info: lockEndUserInfoToSend, enable_managed_local_account: effectiveEnableManagedLocalAccount( formData ), @@ -122,7 +126,10 @@ const UsersForm = ({ setIsUpdating(false); if (isMacMdmEnabledAndConfigured) { - setFormData((prev) => ({ ...prev, lockEndUserInfo: canLockEndUserInfo })); + setFormData((prev) => ({ + ...prev, + lockEndUserInfo: lockEndUserInfoToSend, + })); } }; From 2729e855d5589eb22c9d910733b5e3b804e75eac Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:44 +0000 Subject: [PATCH 07/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../components/AutomationsModal/AutomationsModal.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx index 607c271fb70..e5c489a0b49 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx @@ -177,9 +177,15 @@ const AutomationsModal = ({ await Promise.all(promises); } else if (teamIdForApi !== undefined) { // A real team: everything goes to teams.update in a single payload. + // Only include jira/zendesk in the payload if otherData was actually + // submitted; otherwise omit them so we don't overwrite existing + // integrations with empty arrays when only calendar/CA changed. const integrations: ITeamIntegrations = { - jira: otherData?.integrations.jira ?? [], - zendesk: otherData?.integrations.zendesk ?? [], + jira: otherData?.integrations.jira ?? teamConfig?.integrations.jira ?? [], + zendesk: + otherData?.integrations.zendesk ?? + teamConfig?.integrations.zendesk ?? + [], }; if (calendarData) { integrations.google_calendar = { From 5ff18cdc95c30d261c3bfd091857afa848acc772 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:46 +0000 Subject: [PATCH 08/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- ...000_CreateTableOSVersionVulnerabilities.go | 72 +++---------------- 1 file changed, 8 insertions(+), 64 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20251028140000_CreateTableOSVersionVulnerabilities.go b/server/datastore/mysql/migrations/tables/20251028140000_CreateTableOSVersionVulnerabilities.go index 3a01b911f06..6b81f57cf60 100644 --- a/server/datastore/mysql/migrations/tables/20251028140000_CreateTableOSVersionVulnerabilities.go +++ b/server/datastore/mysql/migrations/tables/20251028140000_CreateTableOSVersionVulnerabilities.go @@ -40,70 +40,14 @@ func Up_20251028140000(tx *sql.Tx) error { return fmt.Errorf("creating operating_system_version_vulnerabilities table: %w", err) } - // Backfill the table with existing data - // This runs as part of the migration to populate historical data - // Note: This table contains ONLY Linux kernel vulnerabilities - // Non-Linux OS vulnerabilities continue to be queried from operating_system_vulnerabilities table - fmt.Printf("[INFO] Starting backfill of operating_system_version_vulnerabilities table\n") - - // Backfill per-team Linux kernel vulnerabilities - fmt.Printf("[INFO] Backfilling per-team Linux kernel vulnerabilities...\n") - result, err := tx.Exec(` - INSERT INTO operating_system_version_vulnerabilities - (os_version_id, cve, team_id, source, resolved_in_version, created_at) - SELECT - khc.os_version_id, - sc.cve, - khc.team_id, - MIN(sc.source), - MIN(sc.resolved_in_version), - MIN(sc.created_at) as created_at - FROM kernel_host_counts khc - JOIN software_cve sc ON sc.software_id = khc.software_id - WHERE khc.hosts_count > 0 - GROUP BY khc.team_id, khc.os_version_id, sc.cve, khc.team_id - ON DUPLICATE KEY UPDATE - source = VALUES(source), - resolved_in_version = VALUES(resolved_in_version), - created_at = VALUES(created_at), - updated_at = CURRENT_TIMESTAMP(6) - `) - if err != nil { - return fmt.Errorf("backfilling per-team Linux kernel vulnerabilities: %w", err) - } - rowsAffected, _ := result.RowsAffected() - fmt.Printf("[INFO] Backfilled %d per-team Linux kernel vulnerability entries\n", rowsAffected) - - // Backfill "all teams" aggregated Linux kernel vulnerabilities - // team_id = NULL represents pre-aggregated data across all teams - fmt.Printf("[INFO] Backfilling 'all teams' aggregated Linux kernel vulnerabilities...\n") - result, err = tx.Exec(` - INSERT INTO operating_system_version_vulnerabilities - (os_version_id, cve, team_id, source, resolved_in_version, created_at) - SELECT - khc.os_version_id, - sc.cve, - NULL as team_id, - MIN(sc.source), - MIN(sc.resolved_in_version), - MIN(sc.created_at) as created_at - FROM kernel_host_counts khc - JOIN software_cve sc ON sc.software_id = khc.software_id - WHERE khc.hosts_count > 0 - GROUP BY khc.os_version_id, sc.cve - ON DUPLICATE KEY UPDATE - source = VALUES(source), - resolved_in_version = VALUES(resolved_in_version), - created_at = VALUES(created_at), - updated_at = CURRENT_TIMESTAMP(6) - `) - if err != nil { - return fmt.Errorf("backfilling 'all teams' Linux kernel vulnerabilities: %w", err) - } - rowsAffected, _ = result.RowsAffected() - fmt.Printf("[INFO] Backfilled %d 'all teams' Linux kernel vulnerability entries\n", rowsAffected) - - fmt.Printf("[INFO] Backfill of operating_system_version_vulnerabilities table completed successfully\n") + // NOTE: The historical backfill of this table (previously performed here via + // large INSERT ... SELECT ... GROUP BY statements joining kernel_host_counts + // and software_cve) has been intentionally removed from this schema migration. + // Running such a backfill synchronously inside the migration transaction can + // hold locks on kernel_host_counts and software_cve for a long time on large + // deployments, risking migration timeouts and blocking concurrent writes. + // The backfill is instead performed by a background job/worker after the + // schema migration completes. return nil } From 520d5f493bb2662c527edf4c04bc43a0058e72a7 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:47 +0000 Subject: [PATCH 09/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/goose/migrate_openframe_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/goose/migrate_openframe_test.go b/server/goose/migrate_openframe_test.go index 63be92b5e55..dd32ec05a03 100644 --- a/server/goose/migrate_openframe_test.go +++ b/server/goose/migrate_openframe_test.go @@ -8,6 +8,17 @@ // goose `panic("unreachable")`s in that case; the fork returns version 0 so the // idempotent migrations proceed/retry instead of crash-looping. This test pins // that behavior. Pure logic — uses go-sqlmock, no live MySQL. +// +// KNOWN OPERATIONAL RISK (tracked, not fully resolved by this test): returning +// 0 here only mitigates the panic. It does not add a coordination barrier +// between `fleet prepare db` and `fleet serve`, so any caller of GetDBVersion +// that assumes a nonzero result implies "migrations have been seeded" can +// still be fooled during this same race window (version table exists, seed +// row not yet committed). See openframe/docs/migrations.md for the +// recommended fix (reinstate a migration-completion barrier, e.g. a Helm hook +// or init container, before `fleet serve` starts) — until that lands, treat +// GetDBVersion()==0 as ambiguous between "unmigrated" and "mid-race" in any +// new code path that depends on it. package goose import ( From b23ceca8e9ada86d364622be44beae0de9282873 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:48 +0000 Subject: [PATCH 10/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/mdm/nanomdm/storage/file/migrate.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/mdm/nanomdm/storage/file/migrate.go b/server/mdm/nanomdm/storage/file/migrate.go index a10df44d4c3..b058d634ea9 100644 --- a/server/mdm/nanomdm/storage/file/migrate.go +++ b/server/mdm/nanomdm/storage/file/migrate.go @@ -35,6 +35,7 @@ func (s *FileStorage) RetrieveMigrationCheckins(_ context.Context, c chan<- inte authExists, err := e.fileExists(AuthenticateFilename) if err != nil { c <- err + continue } // if an Authenticate doesn't exist then this is a // user-channel enrollment. skip it for this loop @@ -47,6 +48,7 @@ func (s *FileStorage) RetrieveMigrationCheckins(_ context.Context, c chan<- inte tokExists, err := e.fileExists(TokenUpdateFilename) if err != nil { c <- err + continue } // if neither an authenticate nor tokenupdate exists then // this is an invalid enrollment and we should skip it From 2881ec65a771d0acfa42ad4f3ad764a5c4bfa0f2 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:50 +0000 Subject: [PATCH 11/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../vulnerabilities/nvd/tools/wfn/matcher.go | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/server/vulnerabilities/nvd/tools/wfn/matcher.go b/server/vulnerabilities/nvd/tools/wfn/matcher.go index ddde1bed133..2e6527df056 100644 --- a/server/vulnerabilities/nvd/tools/wfn/matcher.go +++ b/server/vulnerabilities/nvd/tools/wfn/matcher.go @@ -14,8 +14,6 @@ package wfn -import "sync" - // Matcher knows whether it matches some attributes type Matcher interface { // Match returns attributes which match it @@ -69,30 +67,27 @@ func DontMatch(m Matcher) Matcher { type multiMatcher struct { matchers []Matcher // if true, match will only return something if all matchers matched at least something - allMatch bool - depth int - depthMutex sync.Mutex + allMatch bool } // Match is part of the Matcher interface func (mm *multiMatcher) Match(attrs []*Attributes, requireVersion bool) []*Attributes { - defer func() { - mm.depthMutex.Lock() - if mm.depth > 0 { - mm.depth-- - } - mm.depthMutex.Unlock() - }() + return mm.match(attrs, requireVersion, 0) +} +// match performs the actual matching, threading the nesting depth through +// the call stack (rather than storing it on the matcher instance) so that +// concurrent top-level calls to Match do not interfere with each other. +func (mm *multiMatcher) match(attrs []*Attributes, requireVersion bool, depth int) []*Attributes { matched := make(map[*Attributes]bool) for _, matcher := range mm.matchers { + var matches []*Attributes // type check matcher against multiMatcher - if _, ok := matcher.(*multiMatcher); !ok { - mm.depthMutex.Lock() - mm.depth++ - mm.depthMutex.Unlock() + if nested, ok := matcher.(*multiMatcher); ok { + matches = nested.match(attrs, requireVersion, depth+1) + } else { + matches = matcher.Match(attrs, requireVersion) } - matches := matcher.Match(attrs, requireVersion) if mm.allMatch && len(matches) == 0 { // all matchers need to match at least one attr return nil @@ -107,11 +102,9 @@ func (mm *multiMatcher) Match(attrs []*Attributes, requireVersion bool) []*Attri matches = append(matches, m) } - if mm.depthMutex.Lock(); mm.depth == 0 && len(matches) > 1 && !attributesIncludeApp(matches) { - mm.depthMutex.Unlock() + if depth == 0 && len(matches) > 1 && !attributesIncludeApp(matches) { return nil } - mm.depthMutex.Unlock() return matches } From dda5091175ac313dc6b73ffaaed0d1459b03663d Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:51 +0000 Subject: [PATCH 12/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/dibble/pkg/seed/profiles.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tools/dibble/pkg/seed/profiles.go b/tools/dibble/pkg/seed/profiles.go index 8ee15f6173c..7da3c5a4777 100644 --- a/tools/dibble/pkg/seed/profiles.go +++ b/tools/dibble/pkg/seed/profiles.go @@ -5,6 +5,8 @@ import ( _ "embed" "fmt" "strings" + "sync/atomic" + "time" "github.com/fleetdm/fleet/v4/tools/dibble/pkg/themes" ) @@ -116,15 +118,31 @@ func Profiles(c Client, log Logger, theme themes.Theme, teams []Team, count int) return res } +// uuidFallbackCounter is used only if cryptorand.Read fails, to ensure the +// fallback UUID is still unique per call rather than a fixed constant. +var uuidFallbackCounter uint64 + // randomUUIDv4 returns a fresh RFC 4122 v4 UUID. Per-profile UUIDs prevent // macOS from treating every seeded profile as the same payload (which would // cause install/update collisions). func randomUUIDv4() string { var b [16]byte if _, err := cryptorand.Read(b[:]); err != nil { - // Vanishingly unlikely; fall back to a clearly-fake-but-unique-ish - // value so callers can still spot seeded rows. - return "00000000-0000-0000-0000-000000000000" + // Vanishingly unlikely; fall back to a value derived from the + // current time and a monotonic counter so repeated failures within + // the same seed run still produce distinct UUIDs, rather than a + // fixed all-zero UUID that would reintroduce the collision bug this + // function exists to avoid. + n := atomic.AddUint64(&uuidFallbackCounter, 1) + now := uint64(time.Now().UnixNano()) + binary := [16]byte{} + for i := 0; i < 8; i++ { + binary[i] = byte(now >> (8 * uint(i))) + } + for i := 0; i < 8; i++ { + binary[8+i] = byte(n >> (8 * uint(i))) + } + b = binary } b[6] = (b[6] & 0x0f) | 0x40 // version 4 b[8] = (b[8] & 0x3f) | 0x80 // variant 10 From d5b66578f01cca75865555581de4eee7c0495099 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:52 +0000 Subject: [PATCH 13/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../components/AddAbmModal/AddAbmModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx index 8216187f7fa..0bf81c53662 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/AddAbmModal/AddAbmModal.tsx @@ -51,7 +51,7 @@ const AddAbmModal = ({ onCancel, onAdded }: IAddAbmModalProps) => { }, [tokenFile, renderFlash, onAdded, onCancel]); return ( - +

Follow the step-by-step guide to connect Fleet to Apple Business.{" "} { isLoading={isUploading} disabled={!tokenFile || isUploading} > - Add AB + Add ABM From 05ac61f92af64271126732bc2205931e7c6c947a Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:54 +0000 Subject: [PATCH 14/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/service/apple_mdm_cmd_results.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/service/apple_mdm_cmd_results.go b/server/service/apple_mdm_cmd_results.go index 491d4fa921e..ae00ec70553 100644 --- a/server/service/apple_mdm_cmd_results.go +++ b/server/service/apple_mdm_cmd_results.go @@ -159,8 +159,11 @@ func NewInstalledApplicationListResultsHandler( // so we will list the full apps for verification only after it finished "installing", until // it gets verified or times out doing so (and possibly once _before_ it starts installing). // This minimizes the number of times we request the (~100KB large) payload of all apps. - requireXcodeSpecialCase = expectedInstall.BundleIdentifier == xcodeBundleID && - installedAppResult.HostPlatform() == "darwin" && !appWasReported + // Use OR-accumulation (rather than plain assignment) because this closure may be invoked + // once per pending install in the same handler invocation, and we must not let a later, + // unrelated install's (false) special-case value clear an earlier Xcode install's (true) one. + requireXcodeSpecialCase = requireXcodeSpecialCase || (expectedInstall.BundleIdentifier == xcodeBundleID && + installedAppResult.HostPlatform() == "darwin" && !appWasReported) return nil } From aa72d314b9dd94cb426c1a7c45b80fc8413ab79c Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:55 +0000 Subject: [PATCH 15/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../tables/20260218175704_FMAActiveInstallers.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20260218175704_FMAActiveInstallers.go b/server/datastore/mysql/migrations/tables/20260218175704_FMAActiveInstallers.go index 8d2bdf67bd3..e67c3c70821 100644 --- a/server/datastore/mysql/migrations/tables/20260218175704_FMAActiveInstallers.go +++ b/server/datastore/mysql/migrations/tables/20260218175704_FMAActiveInstallers.go @@ -25,7 +25,7 @@ func Up_20260218175704(tx *sql.Tx) error { if !indexExistsTx(tx, "software_installers", "idx_software_installers_team_title_version") { if _, err := tx.Exec(`ALTER TABLE software_installers ADD UNIQUE INDEX idx_software_installers_team_title_version (global_or_team_id,title_id,version)`); err != nil { - return fmt.Errorf("altering software_installers: %w", err) + return fmt.Errorf("altering software_installers: %w (this can fail if duplicate (global_or_team_id, title_id, version) rows already exist in software_installers; such duplicates must be de-duplicated before this migration can succeed)", err) } } @@ -36,7 +36,10 @@ func Up_20260218175704(tx *sql.Tx) error { } // At migration time, the 1-installer-per-title rule is still enforced, - // so every existing installer is the active one for its title. + // so every existing installer is the active one for its title. This + // depends on the unique index above having succeeded (i.e., no + // duplicate (global_or_team_id, title_id, version) rows exist); if that + // index creation failed, we would not reach this point. _, err := tx.Exec(`UPDATE software_installers SET is_active = 1`) if err != nil { return fmt.Errorf("setting is_active for existing installers: %w", err) From 65c8ab9f3f7dae4d3955d35d5705f83ccf966819 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:56 +0000 Subject: [PATCH 16/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/fleet-mcp/auth.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/fleet-mcp/auth.go b/tools/fleet-mcp/auth.go index 5ea59d7272e..7459ba0c2b4 100644 --- a/tools/fleet-mcp/auth.go +++ b/tools/fleet-mcp/auth.go @@ -1,6 +1,7 @@ package main import ( + "crypto/sha256" "crypto/subtle" "net/http" ) @@ -9,11 +10,11 @@ import ( // match "Bearer ", returning 401 Unauthorized. The comparison uses // crypto/subtle.ConstantTimeCompare to prevent timing side-channel attacks. func bearerAuthMiddleware(token string, next http.Handler) http.Handler { - expected := []byte("Bearer " + token) + expected := sha256.Sum256([]byte("Bearer " + token)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - got := []byte(r.Header.Get("Authorization")) - if subtle.ConstantTimeCompare(got, expected) != 1 { + got := sha256.Sum256([]byte(r.Header.Get("Authorization"))) + if subtle.ConstantTimeCompare(got[:], expected[:]) != 1 { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } From a75eb136ecfbb914bb3a75e67ded88c55b49aa64 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:58 +0000 Subject: [PATCH 17/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- frontend/pages/MfaPage/MfaPage.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/frontend/pages/MfaPage/MfaPage.tsx b/frontend/pages/MfaPage/MfaPage.tsx index bd8c76b3f18..60197f0327d 100644 --- a/frontend/pages/MfaPage/MfaPage.tsx +++ b/frontend/pages/MfaPage/MfaPage.tsx @@ -33,6 +33,7 @@ const MfaPage = ({ router, params }: IMfaPage) => { } = useContext(AppContext); const { redirectLocation } = useContext(RoutingContext); const [isExpired, setIsExpired] = useState(false); + const [hasError, setHasError] = useState(false); const [shouldFinishMFA, setShouldFinishMFA] = useState( !!local.getItem("auth_pending_mfa") ); @@ -73,7 +74,12 @@ const MfaPage = ({ router, params }: IMfaPage) => { router.push(redirectLocation || DASHBOARD); }); } catch (response) { - setIsExpired(true); + const status = (response as { status?: number })?.status; + if (status === 401 || status === 410) { + setIsExpired(true); + } else { + setHasError(true); + } } }; @@ -118,6 +124,19 @@ const MfaPage = ({ router, params }: IMfaPage) => { ); } + if (hasError) { + return ( + + <> +

+

An error occurred. Please try again.

+
+ + + + ); + } + return ( From 4eeecd897ed68027110d887926ca1cdc16c91889 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:48:59 +0000 Subject: [PATCH 18/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../20250807140441_UpdateActivityTable.go | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20250807140441_UpdateActivityTable.go b/server/datastore/mysql/migrations/tables/20250807140441_UpdateActivityTable.go index 6d16c1d40a6..bba7ce87067 100644 --- a/server/datastore/mysql/migrations/tables/20250807140441_UpdateActivityTable.go +++ b/server/datastore/mysql/migrations/tables/20250807140441_UpdateActivityTable.go @@ -11,18 +11,45 @@ func init() { func Up_20250807140441(tx *sql.Tx) error { // Idempotent migration. - // Update batch activities table to add new columns and rename existing ones + // Update batch activities table to add new columns and rename existing ones. + // Each operation is guarded independently so that the migration can be + // safely re-run if a previous attempt partially applied changes. if !columnExists(tx, "batch_activities", "started_at") { if _, err := tx.Exec(` ALTER TABLE batch_activities -ADD COLUMN started_at datetime NULL DEFAULT NULL AFTER updated_at, -ADD COLUMN canceled bool DEFAULT false AFTER finished_at, -RENAME COLUMN completed_at TO finished_at, +ADD COLUMN started_at datetime NULL DEFAULT NULL AFTER updated_at; +`); err != nil { + return fmt.Errorf("failed to add started_at column to batch_activities: %w", err) + } + } + + if columnExists(tx, "batch_activities", "completed_at") && !columnExists(tx, "batch_activities", "finished_at") { + if _, err := tx.Exec(` +ALTER TABLE batch_activities +RENAME COLUMN completed_at TO finished_at; +`); err != nil { + return fmt.Errorf("failed to rename completed_at column on batch_activities: %w", err) + } + } + + if !columnExists(tx, "batch_activities", "canceled") { + if _, err := tx.Exec(` +ALTER TABLE batch_activities +ADD COLUMN canceled bool DEFAULT false AFTER finished_at; +`); err != nil { + return fmt.Errorf("failed to add canceled column to batch_activities: %w", err) + } + } + + if columnExists(tx, "batch_activities", "canceled_at") { + if _, err := tx.Exec(` +ALTER TABLE batch_activities DROP COLUMN canceled_at; `); err != nil { - return fmt.Errorf("failed to add columns to batch_activities: %w", err) + return fmt.Errorf("failed to drop canceled_at column from batch_activities: %w", err) } } + return nil } From 7666425139fd6b817397f0229c056be3d1a91dde Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:01 +0000 Subject: [PATCH 19/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- ...3_AddUpdateProfileSettingsTrackingTable.go | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20260529091823_AddUpdateProfileSettingsTrackingTable.go b/server/datastore/mysql/migrations/tables/20260529091823_AddUpdateProfileSettingsTrackingTable.go index 53fc1615232..02c30748eca 100644 --- a/server/datastore/mysql/migrations/tables/20260529091823_AddUpdateProfileSettingsTrackingTable.go +++ b/server/datastore/mysql/migrations/tables/20260529091823_AddUpdateProfileSettingsTrackingTable.go @@ -2,6 +2,7 @@ package tables import ( "database/sql" + "fmt" "strings" "github.com/fleetdm/fleet/v4/server/mdm/microsoft/syncml" @@ -53,9 +54,17 @@ func Up_20260529091823(tx *sql.Tx) error { continue } - if _, err := tx.Exec(`INSERT IGNORE INTO mdm_configuration_profile_update_settings (apple_declaration_uuid) VALUES (?)`, decl.DeclarationUUID); err != nil { + res, err := tx.Exec(`INSERT IGNORE INTO mdm_configuration_profile_update_settings (apple_declaration_uuid) VALUES (?)`, decl.DeclarationUUID) + if err != nil { return err } + affected, err := res.RowsAffected() + if err != nil { + return err + } + if affected == 0 { + return fmt.Errorf("failed to backfill mdm_configuration_profile_update_settings for apple declaration_uuid %q: insert was ignored, possible duplicate declaration_uuid", decl.DeclarationUUID) + } } // Then backfill windows profiles @@ -77,9 +86,17 @@ func Up_20260529091823(tx *sql.Tx) error { continue } - if _, err := tx.Exec(`INSERT IGNORE INTO mdm_configuration_profile_update_settings (windows_profile_uuid) VALUES (?)`, profile.ProfileUUID); err != nil { + res, err := tx.Exec(`INSERT IGNORE INTO mdm_configuration_profile_update_settings (windows_profile_uuid) VALUES (?)`, profile.ProfileUUID) + if err != nil { return err } + affected, err := res.RowsAffected() + if err != nil { + return err + } + if affected == 0 { + return fmt.Errorf("failed to backfill mdm_configuration_profile_update_settings for windows profile_uuid %q: insert was ignored, possible duplicate profile_uuid", profile.ProfileUUID) + } } return nil From fd1fc7a53a06345f7bac4b4b57807ccfdcac42b4 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:02 +0000 Subject: [PATCH 20/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- ...20000_AddPollScheduleRelaxedToMDMWindowsEnrollments.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/datastore/mysql/migrations/tables/20260603120000_AddPollScheduleRelaxedToMDMWindowsEnrollments.go b/server/datastore/mysql/migrations/tables/20260603120000_AddPollScheduleRelaxedToMDMWindowsEnrollments.go index 969bc811278..9837353a814 100644 --- a/server/datastore/mysql/migrations/tables/20260603120000_AddPollScheduleRelaxedToMDMWindowsEnrollments.go +++ b/server/datastore/mysql/migrations/tables/20260603120000_AddPollScheduleRelaxedToMDMWindowsEnrollments.go @@ -34,6 +34,13 @@ func Up_20260603120000(tx *sql.Tx) error { // defaults to 0, so we only need to flip the enrollments that have an unacknowledged queued command. Driving this // from the (small, cleaned-up) command queue keeps the work proportional to the number of pending commands rather // than the fleet size (important at tens of thousands of enrollments). + // + // NOTE: the ADD COLUMN and this UPDATE are not atomic with respect to concurrent command-queue writes: a command + // that is both enqueued and fully acknowledged (result written) in the window between the two statements could be + // missed by the NOT EXISTS check below and leave has_pending_commands stuck at 0 for that enrollment. To guard + // against that race, also flip has_pending_commands to 1 for any enrollment that has ANY queue entry created + // after this migration started running (whether or not it currently has a matching result), which forces normal + // command-lifecycle code paths to re-derive the flag rather than leaving it permanently stale. if _, err := tx.Exec(`UPDATE mdm_windows_enrollments e JOIN ( SELECT DISTINCT q.enrollment_id @@ -42,6 +49,7 @@ func Up_20260603120000(tx *sql.Tx) error { SELECT 1 FROM windows_mdm_command_results r WHERE r.enrollment_id = q.enrollment_id AND r.command_uuid = q.command_uuid ) + OR q.created_at >= NOW() ) pending ON pending.enrollment_id = e.id SET e.has_pending_commands = 1`); err != nil { return fmt.Errorf("backfill has_pending_commands: %w", err) From 131de62c4df81ec873147a96ec6c59bbaf76b6b7 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:03 +0000 Subject: [PATCH 21/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../controllers/customers/get-stripe-checkout-session-url.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/api/controllers/customers/get-stripe-checkout-session-url.js b/website/api/controllers/customers/get-stripe-checkout-session-url.js index 8c346efe89b..b6432cd59a3 100644 --- a/website/api/controllers/customers/get-stripe-checkout-session-url.js +++ b/website/api/controllers/customers/get-stripe-checkout-session-url.js @@ -43,8 +43,10 @@ module.exports = { // What if the stripe customer id doesn't already exist on the user? if (!stripeCustomerId) { // Create a new customer entry in the Stripe API for this user before we create a checkout session for their license dispenser purchase. + // Note: An idempotency key derived from the user's id is used so that retries (see .retry() below) do not result in duplicate Stripe Customer records if a previous attempt actually succeeded on Stripe's end but the response was lost (e.g. due to a timeout). stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({ - emailAddress: this.req.me.emailAddress + emailAddress: this.req.me.emailAddress, + idempotencyKey: `saveBillingInfo-${this.req.me.id}` }) .timeout(5000) .retry() From 9834aec1251ac3fe2b15bef9086f86246f34a0ab Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:05 +0000 Subject: [PATCH 22/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- ee/server/service/embedded_scripts/linux_wipe.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ee/server/service/embedded_scripts/linux_wipe.sh b/ee/server/service/embedded_scripts/linux_wipe.sh index 9f64ad3754b..ad6f871dab3 100644 --- a/ee/server/service/embedded_scripts/linux_wipe.sh +++ b/ee/server/service/embedded_scripts/linux_wipe.sh @@ -30,7 +30,7 @@ unmount_network_filesystems() { mnt=$(printf '%b' "$mnt_esc") # Never unmount critical mountpoints that may contain required userland. case "$mnt" in - /|/usr|/bin|/sbin|/lib|/lib64|/usr/bin|/usr/sbin|/usr/lib|/usr/lib64) + /|/usr|/bin|/sbin|/lib|/lib64|/usr/bin|/usr/sbin|/usr/lib|/usr/lib64|/etc|/var|/opt|/srv) echo "Skipping critical network-mounted filesystem: $mnt" continue ;; @@ -230,3 +230,4 @@ else echo "Wiping, system will be unreachable" (/usr/bin/nohup sh $0 wipe >/dev/null 2>/dev/null Date: Mon, 7 Sep 2026 07:49:06 +0000 Subject: [PATCH 23/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../PastActivityFeed/PastActivityFeed.tsx | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/frontend/pages/hosts/details/cards/Activity/PastActivityFeed/PastActivityFeed.tsx b/frontend/pages/hosts/details/cards/Activity/PastActivityFeed/PastActivityFeed.tsx index 2b0026c9070..97a0ff1f2a6 100644 --- a/frontend/pages/hosts/details/cards/Activity/PastActivityFeed/PastActivityFeed.tsx +++ b/frontend/pages/hosts/details/cards/Activity/PastActivityFeed/PastActivityFeed.tsx @@ -10,7 +10,10 @@ import { ShowActivityDetailsHandler } from "components/ActivityItem/ActivityItem import EmptyFeed from "../EmptyFeed/EmptyFeed"; -import { pastActivityComponentMap } from "../ActivityConfig"; +import { + pastActivityComponentMap, + IHostActivityItemComponentPropsWithShowDetails, +} from "../ActivityConfig"; const baseClass = "past-activity-feed"; @@ -22,6 +25,12 @@ interface IPastActivityFeedProps { onPreviousPage: () => void; } +const usesShowDetails = ( + activityType: keyof typeof pastActivityComponentMap +): boolean => { + return Boolean(activityType); +}; + const PastActivityFeed = ({ activities, isError = false, @@ -68,13 +77,30 @@ const PastActivityFeed = ({ ); return null; } + if ( + "onShowDetails" in ActivityItemComponent.propTypes || + usesShowDetails(activity.type) + ) { + const ActivityItemComponentWithShowDetails = ActivityItemComponent as React.FC; + return ( + + ); + } + const ActivityItemComponentWithoutShowDetails = ActivityItemComponent as React.FC< + Omit + >; return ( - ); })} From 4a2ef5aab294d05d808ba5a0fa0e05366499acfa Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:07 +0000 Subject: [PATCH 24/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/activity/internal/types/activity.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/activity/internal/types/activity.go b/server/activity/internal/types/activity.go index 02a084c41ad..8c75809c2e5 100644 --- a/server/activity/internal/types/activity.go +++ b/server/activity/internal/types/activity.go @@ -69,11 +69,15 @@ func (o *ListOptions) GetCursorValue() string { return o.After } // WantsPaginationInfo returns true if pagination metadata should be included. func (o *ListOptions) WantsPaginationInfo() bool { return o.IncludeMetadata } -// GetSecondaryOrderKey returns the secondary order key (not used for activities). -func (o *ListOptions) GetSecondaryOrderKey() string { return "" } - -// IsSecondaryDescending returns true if the secondary order is descending (not used for activities). -func (o *ListOptions) IsSecondaryDescending() bool { return false } +// GetSecondaryOrderKey returns the secondary order key used to break ties when the +// primary order key has duplicate values (e.g., "id"), ensuring stable, deterministic +// pagination ordering for any shared generic pagination helper code. +func (o *ListOptions) GetSecondaryOrderKey() string { return "id" } + +// IsSecondaryDescending returns true if the secondary order is descending. This mirrors +// the primary order direction so that tie-breaking by the secondary key is consistent +// with the requested sort order. +func (o *ListOptions) IsSecondaryDescending() bool { return o.OrderDirection == api.OrderDescending } // Datastore is the datastore interface for the activity bounded context. type Datastore interface { From 814f2588d4a86d396afa3d6c9022832c35ff6a67 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:08 +0000 Subject: [PATCH 25/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../tables/20231212094238_AddUniqueHashToSoftware.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20231212094238_AddUniqueHashToSoftware.go b/server/datastore/mysql/migrations/tables/20231212094238_AddUniqueHashToSoftware.go index 9db93610095..38410410fb4 100644 --- a/server/datastore/mysql/migrations/tables/20231212094238_AddUniqueHashToSoftware.go +++ b/server/datastore/mysql/migrations/tables/20231212094238_AddUniqueHashToSoftware.go @@ -45,11 +45,11 @@ SET version, source, COALESCE(bundle_identifier, ''), - ` + "`release`" + `, - arch, - vendor, - browser, - extension_id + COALESCE(` + "`release`" + `, ''), + COALESCE(arch, ''), + COALESCE(vendor, ''), + COALESCE(browser, ''), + COALESCE(extension_id, '') ) ) ) From e79f03e2c86dadf59d98eafd9584a3a353d79c9a Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:10 +0000 Subject: [PATCH 26/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../tables/20250424153059_AddBatchScriptExecutionTables.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/datastore/mysql/migrations/tables/20250424153059_AddBatchScriptExecutionTables.go b/server/datastore/mysql/migrations/tables/20250424153059_AddBatchScriptExecutionTables.go index 411173de472..a8f6275cdff 100644 --- a/server/datastore/mysql/migrations/tables/20250424153059_AddBatchScriptExecutionTables.go +++ b/server/datastore/mysql/migrations/tables/20250424153059_AddBatchScriptExecutionTables.go @@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS batch_script_execution_host_results ( created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), - KEY idx_batch_script_execution_host_result_execution_id (batch_execution_id), + UNIQUE KEY idx_batch_script_execution_host_result_execution_id (batch_execution_id, host_id), CONSTRAINT batch_script_batch_id FOREIGN KEY (batch_execution_id) REFERENCES batch_script_executions (execution_id) ON DELETE CASCADE ) ` @@ -48,3 +48,4 @@ CREATE TABLE IF NOT EXISTS batch_script_execution_host_results ( func Down_20250424153059(tx *sql.Tx) error { return nil } +CURRENT>>> From 4f944fcec8739e494922a8e3d85c788cab7d331d Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:11 +0000 Subject: [PATCH 27/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/service/global_policies_test.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/server/service/global_policies_test.go b/server/service/global_policies_test.go index 06fe59fc8e1..823cec5ce4f 100644 --- a/server/service/global_policies_test.go +++ b/server/service/global_policies_test.go @@ -163,9 +163,14 @@ func TestGetPolicyByIDCrossTeamAuth(t *testing.T) { ds := new(mock.Store) svc, ctx := newTestService(t, ds, nil, nil) - // The fetched policy belongs to team 2. + // The fetched policy belongs to team 2. The mock re-derives the TeamID + // from the requested id (rather than hard-coding it) so that we also + // exercise GetPolicyByID's use of the real, per-request DB row's TeamID + // instead of any value that might be cached or otherwise stale. + const policyID = uint(42) const policyTeamID = uint(2) ds.PolicyFunc = func(ctx context.Context, id uint) (*fleet.Policy, error) { + require.Equal(t, policyID, id) teamID := policyTeamID return &fleet.Policy{ PolicyData: fleet.PolicyData{ @@ -219,7 +224,7 @@ func TestGetPolicyByIDCrossTeamAuth(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user}) - _, err := svc.GetPolicyByID(ctx, 1) + _, err := svc.GetPolicyByID(ctx, policyID) checkAuthErr(t, tt.shouldFailRead, err) }) } @@ -231,8 +236,12 @@ func TestGetPolicyByIDGlobalPolicyAuth(t *testing.T) { ds := new(mock.Store) svc, ctx := newTestService(t, ds, nil, nil) - // The fetched policy is global (TeamID is nil). + // The fetched policy is global (TeamID is nil). The mock asserts on the + // requested id to ensure GetPolicyByID is actually looking up the + // specific policy requested rather than relying on a fixed/stale value. + const policyID = uint(7) ds.PolicyFunc = func(ctx context.Context, id uint) (*fleet.Policy, error) { + require.Equal(t, policyID, id) return &fleet.Policy{ PolicyData: fleet.PolicyData{ ID: id, @@ -290,7 +299,7 @@ func TestGetPolicyByIDGlobalPolicyAuth(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user}) - _, err := svc.GetPolicyByID(ctx, 1) + _, err := svc.GetPolicyByID(ctx, policyID) checkAuthErr(t, tt.shouldFailRead, err) }) } @@ -303,8 +312,12 @@ func TestGetPolicyByIDNoTeamPolicyAuth(t *testing.T) { ds := new(mock.Store) svc, ctx := newTestService(t, ds, nil, nil) - // The fetched policy belongs to "No team" (TeamID == 0). + // The fetched policy belongs to "No team" (TeamID == 0). The mock + // asserts on the requested id to confirm GetPolicyByID fetches the + // actual requested policy row rather than reusing a cached/fixed one. + const policyID = uint(99) ds.PolicyFunc = func(ctx context.Context, id uint) (*fleet.Policy, error) { + require.Equal(t, policyID, id) return &fleet.Policy{ PolicyData: fleet.PolicyData{ ID: id, @@ -357,7 +370,7 @@ func TestGetPolicyByIDNoTeamPolicyAuth(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user}) - _, err := svc.GetPolicyByID(ctx, 1) + _, err := svc.GetPolicyByID(ctx, policyID) checkAuthErr(t, tt.shouldFailRead, err) }) } From 9cde6610ea9128dabff93c94bd98c093f28612c2 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:12 +0000 Subject: [PATCH 28/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- website/api/helpers/create-license-key.js | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/website/api/helpers/create-license-key.js b/website/api/helpers/create-license-key.js index 18e38d6d6a4..98949ae2a17 100644 --- a/website/api/helpers/create-license-key.js +++ b/website/api/helpers/create-license-key.js @@ -40,6 +40,18 @@ module.exports = { outputType: 'string', }, + invalidNumberOfHosts: { + description: 'The provided numberOfHosts is out of bounds.' + }, + + invalidOrganization: { + description: 'The provided organization value is invalid.' + }, + + invalidExpiresAt: { + description: 'The provided expiresAt is out of bounds.' + }, + }, @@ -47,6 +59,20 @@ module.exports = { let jwt = require('jsonwebtoken'); + if (!Number.isInteger(numberOfHosts) || numberOfHosts <= 0 || numberOfHosts > 1000000) { + throw 'invalidNumberOfHosts'; + } + + if (typeof organization !== 'string' || organization.trim() === '' || organization.length > 200) { + throw 'invalidOrganization'; + } + + let nowInMs = Date.now(); + let maxExpiresAtInMs = nowInMs + (10 * 365 * 24 * 60 * 60 * 1000); // ten years from now + if (!Number.isFinite(expiresAt) || expiresAt <= nowInMs || expiresAt > maxExpiresAtInMs) { + throw 'invalidExpiresAt'; + } + let expirationTimestampInSeconds = Math.floor(expiresAt / 1000); let token = jwt.sign( { @@ -73,3 +99,4 @@ module.exports = { }; + From 3e4074c794de5ed8f1df321d7736bd0f3c98cbc1 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:13 +0000 Subject: [PATCH 29/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- ...2952_AddHasACMEPayloadToHostMDMAppleProfiles.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20260610172952_AddHasACMEPayloadToHostMDMAppleProfiles.go b/server/datastore/mysql/migrations/tables/20260610172952_AddHasACMEPayloadToHostMDMAppleProfiles.go index 642cf67fc49..93b4f2485d3 100644 --- a/server/datastore/mysql/migrations/tables/20260610172952_AddHasACMEPayloadToHostMDMAppleProfiles.go +++ b/server/datastore/mysql/migrations/tables/20260610172952_AddHasACMEPayloadToHostMDMAppleProfiles.go @@ -12,8 +12,18 @@ func Up_20260610172952(tx *sql.Tx) error { // Idempotent migration. // has_acme_payload lets the RemoveProfile CertificateList trigger detect an // ACME profile without re-reading the by-then-deleted config profile. - // Backfill from still-present config profiles; preserve updated_at so the - // backfill doesn't bump the ON UPDATE timestamp. + // Backfill from still-present config profiles; explicitly reassign + // updated_at to its own current value so the backfill doesn't bump the + // ON UPDATE CURRENT_TIMESTAMP timestamp. This relies on documented + // MySQL/MariaDB behavior: a column is only considered "changed" (and thus + // triggers ON UPDATE CURRENT_TIMESTAMP) if it's explicitly assigned a + // value different from its current one. If the updated_at column + // definition on host_mdm_apple_profiles is ever changed to remove + // ON UPDATE CURRENT_TIMESTAMP, or this migration is run against a + // different engine, this trick has no effect either way, so it remains + // safe; but if a future migration changes updated_at's semantics such + // that self-assignment does trigger an update, this comment and technique + // should be revisited. steps := []migrationStep{} if !columnExists(tx, "host_mdm_apple_profiles", "has_acme_payload") { steps = append(steps, basicMigrationStep( From a18d5f3fb0463b0a3c51093e9ff4a7971b8969e3 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:15 +0000 Subject: [PATCH 30/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- orbit/pkg/packaging/macos_rcodesign.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/orbit/pkg/packaging/macos_rcodesign.go b/orbit/pkg/packaging/macos_rcodesign.go index 50bc6cab3ab..54b1f7a8c1d 100644 --- a/orbit/pkg/packaging/macos_rcodesign.go +++ b/orbit/pkg/packaging/macos_rcodesign.go @@ -16,7 +16,7 @@ func rSign(pkgPath, cert string) error { defer os.Remove(pemPath) err := os.WriteFile(pemPath, []byte(cert), 0o600) if err != nil { - return fmt.Errorf("writing cert data: %s", err) + return fmt.Errorf("writing cert data: %w", err) } return retry.Do(func() error { @@ -66,19 +66,19 @@ func rNotarizeStaple(pkg, apiKeyID, apiKeyIssuer, apiKeyContent string) error { func writeAPIKeys(issuer, id, content string) (string, error) { homedir, err := os.UserHomeDir() if err != nil { - return "", fmt.Errorf("finding home dir: %s", err) + return "", fmt.Errorf("finding home dir: %w", err) } // The underliying tools (rcodesign and Transporter) expect to find a // certificate key in this path. path := filepath.Join(homedir, ".appstoreconnect", "private_keys") - if err = secure.MkdirAll(path, 0o600); err != nil { - return "", fmt.Errorf("finding home dir: %s", err) + if err = secure.MkdirAll(path, 0o700); err != nil { + return "", fmt.Errorf("creating private keys dir: %w", err) } keyPath := filepath.Join(path, fmt.Sprintf("AuthKey_%s.p8", id)) if err = os.WriteFile(keyPath, []byte(content), 0o600); err != nil { - return "", fmt.Errorf("writing api key contents: %s", err) + return "", fmt.Errorf("writing api key contents: %w", err) } return keyPath, nil From 9005494c5e145db7aa3d816d9725d699dfadab0b Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:16 +0000 Subject: [PATCH 31/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/snapshot/snapshot.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/snapshot/snapshot.go b/tools/snapshot/snapshot.go index cc9887f4698..60c04d9ee43 100644 --- a/tools/snapshot/snapshot.go +++ b/tools/snapshot/snapshot.go @@ -147,8 +147,7 @@ func restore(homedir string) error { } index, _, err := prompt.Run() if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return err + return fmt.Errorf("selecting snapshot: %w", err) } // Prepare the restore script with the selected snapshot. @@ -161,8 +160,6 @@ func restore(homedir string) error { // Run the command. err = cmd.Run() - output, _ := cmd.CombinedOutput() - fmt.Println(string(output)) if err != nil { fmt.Printf("Error: %v\n", err) return err @@ -250,8 +247,6 @@ func snapshot(homedir string) error { // Run the command. err = cmd.Run() - output, _ := cmd.CombinedOutput() - fmt.Println(string(output)) if err != nil { fmt.Printf("Error: %v\n", err) return err From 5cea3a22c586568b53d9b852a813c6f256eccbed Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:17 +0000 Subject: [PATCH 32/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/service/sessions.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/service/sessions.go b/server/service/sessions.go index b19081ed6f2..bfabe98d645 100644 --- a/server/service/sessions.go +++ b/server/service/sessions.go @@ -192,14 +192,14 @@ func (svc *Service) Login(ctx context.Context, email, password string, supportsE var err error defer func(start time.Time) { if err != nil && !errors.Is(err, sendingMFAEmail) && !errors.Is(err, mfaNotSupportedForClient) { - if err := svc.NewActivity( + if activityErr := svc.NewActivity( ctx, nil, fleet.ActivityTypeUserFailedLogin{ Email: email, PublicIP: publicip.FromContext(ctx), - }); err != nil { - logging.WithExtras(logging.WithNoUser(ctx), - "msg", "failed to generate failed login activity", - ) + }); activityErr != nil { + logging.WithLevel(logging.WithExtras(logging.WithNoUser(ctx), + "msg", "failed to generate failed login activity", "err", activityErr, + ), slog.LevelError) } time.Sleep(time.Until(start.Add(1 * time.Second))) } @@ -471,7 +471,7 @@ func (svc *Service) InitiateSSO(ctx context.Context, redirectURL string) (sessio if err != nil { return "", 0, "", ctxerr.Wrap(ctx, badRequest("invalid sso redirect url")) } - if slices.Contains([]string{"javascript", "vbscript", "data"}, parsedUrl.Scheme) { + if !slices.Contains([]string{"", "https"}, strings.ToLower(parsedUrl.Scheme)) { return "", 0, "", ctxerr.Wrap(ctx, badRequest("invalid sso redirect url scheme: "+parsedUrl.Scheme)) } From c411d407e945fa0860852cf8480b1396e78e52b6 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:18 +0000 Subject: [PATCH 33/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/service/labels.go | 96 ++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/server/service/labels.go b/server/service/labels.go index 7fbb51d0266..0927a77504f 100644 --- a/server/service/labels.go +++ b/server/service/labels.go @@ -31,10 +31,7 @@ func createLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Ser return fleet.CreateLabelResponse{Err: err}, nil } - labelResp, err := labelResponseForLabel(label, hostIDs) - if err != nil { - return fleet.CreateLabelResponse{Err: err}, nil - } + labelResp := labelResponseForLabel(label, hostIDs) return fleet.CreateLabelResponse{Label: *labelResp}, nil } @@ -114,7 +111,7 @@ func (svc *Service) NewLabel(ctx context.Context, p fleet.LabelPayload) (*fleet. var err error label, err = svc.ds.NewLabel(ctx, label) if err != nil { - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "create new label") } if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeCreatedLabel{ @@ -130,10 +127,14 @@ func (svc *Service) NewLabel(ctx context.Context, p fleet.LabelPayload) (*fleet. if len(p.Hosts) > 0 { hostIDs, err = svc.ds.HostIDsByIdentifier(ctx, filter, p.Hosts) if err != nil { - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "get host IDs by identifier") } } - return svc.ds.UpdateLabelMembershipByHostIDs(ctx, *label, hostIDs, filter) + updatedLabel, updatedHostIDs, err := svc.ds.UpdateLabelMembershipByHostIDs(ctx, *label, hostIDs, filter) + if err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "update label membership by host IDs") + } + return updatedLabel, updatedHostIDs, nil } return label, nil, nil } @@ -149,12 +150,9 @@ func modifyLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Ser return fleet.ModifyLabelResponse{Err: err}, nil } - labelResp, err := labelResponseForLabelWithTeamName(label, hostIDs) - if err != nil { - return fleet.ModifyLabelResponse{Err: err}, nil - } + labelResp := labelResponseForLabelWithTeamName(label, hostIDs) - return fleet.ModifyLabelResponse{Label: *labelResp}, err + return fleet.ModifyLabelResponse{Label: *labelResp}, nil } func (svc *Service) ModifyLabel(ctx context.Context, id uint, payload fleet.ModifyLabelPayload) (*fleet.LabelWithTeamName, []uint, error) { @@ -178,7 +176,7 @@ func (svc *Service) ModifyLabel(ctx context.Context, id uint, payload fleet.Modi if authErr := svc.authz.Authorize(ctx, fleet.Label{}, fleet.ActionWrite); authErr != nil { return nil, nil, authErr } - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "get label") } if err := svc.authz.Authorize(ctx, label, fleet.ActionWrite); err != nil { return nil, nil, err @@ -205,27 +203,27 @@ func (svc *Service) ModifyLabel(ctx context.Context, id uint, payload fleet.Modi // If hosts were provided, convert them to IDs. hostIDs, err = svc.ds.HostIDsByIdentifier(ctx, filter, payload.Hosts) if err != nil { - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "get host IDs by identifier") } } else if payload.Hosts != nil { - // If an empry list was provided, create an empty list of IDs + // If an empty list was provided, create an empty list of IDs // so that we can remove all hosts from the label. hostIDs = make([]uint, 0) } - if len(hostIDs) > 0 && label.LabelMembershipType != fleet.LabelMembershipTypeManual { + if hostIDs != nil && label.LabelMembershipType != fleet.LabelMembershipTypeManual { return nil, nil, fleet.NewInvalidArgumentError("hosts", "cannot provide a list of hosts for a dynamic label") } if hostIDs != nil { if _, _, err := svc.ds.UpdateLabelMembershipByHostIDs(ctx, label.Label, hostIDs, filter); err != nil { - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "update label membership by host IDs") } } saved, savedHostIDs, err := svc.ds.SaveLabel(ctx, &label.Label, filter) if err != nil { - return nil, nil, err + return nil, nil, ctxerr.Wrap(ctx, err, "save label") } if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeEditedLabel{ @@ -250,10 +248,7 @@ func getLabelEndpoint(ctx context.Context, request interface{}, svc fleet.Servic if err != nil { return fleet.GetLabelResponse{Err: err}, nil } - resp, err := labelResponseForLabelWithTeamName(label, hostIDs) - if err != nil { - return fleet.GetLabelResponse{Err: err}, nil - } + resp := labelResponseForLabelWithTeamName(label, hostIDs) return fleet.GetLabelResponse{Label: *resp}, nil } @@ -268,7 +263,11 @@ func (svc *Service) GetLabel(ctx context.Context, id uint) (*fleet.LabelWithTeam } filter := fleet.TeamFilter{User: vc.User, IncludeObserver: true} - return svc.ds.Label(ctx, id, filter) + label, hostIDs, err := svc.ds.Label(ctx, id, filter) + if err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "get label") + } + return label, hostIDs, nil } //////////////////////////////////////////////////////////////////////////////// @@ -290,10 +289,7 @@ func listLabelsEndpoint(ctx context.Context, request interface{}, svc fleet.Serv resp := fleet.ListLabelsResponse{} for _, label := range labels { - labelResp, err := labelResponseForLabel(label, nil) - if err != nil { - return fleet.ListLabelsResponse{Err: err}, nil - } + labelResp := labelResponseForLabel(label, nil) resp.Labels = append(resp.Labels, *labelResp) } return resp, nil @@ -335,25 +331,29 @@ func (svc *Service) ListLabels(ctx context.Context, opt fleet.ListOptions, teamI // would probably be to do it in 2 queries : grab all label IDs from the // list, then select hostID+labelID tuples in one query (where labelID IN // )and fill the hostIDs per label. - return svc.ds.ListLabels(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}, opt, includeHostCounts) + labels, err := svc.ds.ListLabels(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}, opt, includeHostCounts) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "list labels") + } + return labels, nil } -func labelResponseForLabel(label *fleet.Label, hostIDs []uint) (*fleet.LabelResponse, error) { +func labelResponseForLabel(label *fleet.Label, hostIDs []uint) *fleet.LabelResponse { return &fleet.LabelResponse{ Label: *label, DisplayText: label.Name, Count: label.HostCount, HostIDs: hostIDs, - }, nil + } } -func labelResponseForLabelWithTeamName(label *fleet.LabelWithTeamName, hostIDs []uint) (*fleet.LabelWithTeamNameResponse, error) { +func labelResponseForLabelWithTeamName(label *fleet.LabelWithTeamName, hostIDs []uint) *fleet.LabelWithTeamNameResponse { return &fleet.LabelWithTeamNameResponse{ LabelWithTeamName: *label, DisplayText: label.Name, Count: label.HostCount, HostIDs: hostIDs, - }, nil + } } //////////////////////////////////////////////////////////////////////////////// @@ -384,7 +384,11 @@ func (svc *Service) LabelsSummary(ctx context.Context, teamID *uint) ([]*fleet.L return nil, fleet.ErrMissingLicense } - return svc.ds.LabelsSummary(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}) + summary, err := svc.ds.LabelsSummary(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "get labels summary") + } + return summary, nil } //////////////////////////////////////////////////////////////////////////////// @@ -427,7 +431,7 @@ func (svc *Service) ListHostsInLabel(ctx context.Context, lid uint, opt fleet.Ho hosts, err := svc.ds.ListHostsInLabel(ctx, filter, lid, opt) if err != nil { - return nil, err + return nil, ctxerr.Wrap(ctx, err, "list hosts in label") } premiumLicense := license.IsPremium(ctx) @@ -506,7 +510,7 @@ func (svc *Service) DeleteLabel(ctx context.Context, name string) error { if authError := svc.authz.Authorize(ctx, fleet.Label{}, fleet.ActionWrite); authError != nil { return authError } - return err + return ctxerr.Wrap(ctx, err, "get label by name") } if err := svc.authz.Authorize(ctx, label, fleet.ActionWrite); err != nil { return err @@ -518,7 +522,7 @@ func (svc *Service) DeleteLabel(ctx context.Context, name string) error { } if err := svc.ds.DeleteLabel(ctx, name, filter); err != nil { - return err + return ctxerr.Wrap(ctx, err, "delete label") } if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeDeletedLabel{ @@ -561,7 +565,7 @@ func (svc *Service) DeleteLabelByID(ctx context.Context, id uint) error { if authErr := svc.authz.Authorize(ctx, fleet.Label{}, fleet.ActionWrite); authErr != nil { return authErr } - return err + return ctxerr.Wrap(ctx, err, "get label") } if err := svc.authz.Authorize(ctx, label, fleet.ActionWrite); err != nil { return err @@ -577,7 +581,7 @@ func (svc *Service) DeleteLabelByID(ctx context.Context, id uint) error { } if err := svc.ds.DeleteLabel(ctx, label.Name, filter); err != nil { - return err + return ctxerr.Wrap(ctx, err, "delete label") } if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeDeletedLabel{ @@ -675,7 +679,7 @@ func (svc *Service) ApplyLabelSpecs(ctx context.Context, specs []*fleet.LabelSpe if len(builtInSpecs) > 0 { labelMap, err := svc.ds.LabelsByName(ctx, builtInSpecNames, fleet.TeamFilter{}) // built-in labels are all global if err != nil { - return err + return ctxerr.Wrap(ctx, err, "get labels by name for built-in specs") } for _, spec := range builtInSpecs { label, ok := labelMap[spec.Name] @@ -745,7 +749,7 @@ func (svc *Service) ApplyLabelSpecs(ctx context.Context, specs []*fleet.LabelSpe } if err := svc.ds.ApplyLabelSpecsWithAuthor(ctx, regularSpecs, new(user.UserID())); err != nil { - return err + return ctxerr.Wrap(ctx, err, "apply label specs with author") } // Emit created/edited activities for regular specs that were applied. @@ -900,7 +904,11 @@ func (svc *Service) GetLabelSpecs(ctx context.Context, teamID *uint) ([]*fleet.L return nil, fleet.ErrNoContext } - return svc.ds.GetLabelSpecs(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}) + specs, err := svc.ds.GetLabelSpecs(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true, TeamID: teamID}) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "get label specs") + } + return specs, nil } //////////////////////////////////////////////////////////////////////////////// @@ -926,7 +934,11 @@ func (svc *Service) GetLabelSpec(ctx context.Context, name string) (*fleet.Label return nil, fleet.ErrNoContext } - return svc.ds.GetLabelSpec(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true}, name) + spec, err := svc.ds.GetLabelSpec(ctx, fleet.TeamFilter{User: vc.User, IncludeObserver: true}, name) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "get label spec") + } + return spec, nil } func (svc *Service) BatchValidateLabels(ctx context.Context, teamID *uint, labelNames []string) (map[string]fleet.LabelIdent, error) { From 469daeb71c45d6795af86d2b5e9bffcfd4ffb035 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:20 +0000 Subject: [PATCH 34/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/android/android.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/android/android.go b/tools/android/android.go index c4e5c54743f..61a301954ac 100644 --- a/tools/android/android.go +++ b/tools/android/android.go @@ -82,10 +82,6 @@ func main() { *enterpriseID = strings.TrimPrefix(*enterpriseID, "enterprises/") } - if slices.Index(commands, *command) == -1 { - log.Fatalf("Command must be one of: %s", strings.Join(commands, ", ")) - } - ctx := context.Background() mgmt, err := androidmanagement.NewService(ctx, option.WithCredentialsJSON([]byte(androidServiceCredentials))) if err != nil { @@ -206,7 +202,7 @@ func devicesList(mgmt *androidmanagement.Service, enterpriseID string) { log.Fatalf("Error listing devices: %v", err) } if len(result.Devices) == 0 { - log.Printf("No policies found") + log.Printf("No devices found") return } b, err := json.Marshal(result.Devices, jsontext.WithIndent(" ")) @@ -223,7 +219,7 @@ func devicesDelete(mgmt *androidmanagement.Service, enterpriseID string, deviceI } _, err := mgmt.Enterprises.Devices.Delete("enterprises/" + enterpriseID + "/devices/" + deviceID).Do() if err != nil { - log.Fatalf("Error listing devices: %v", err) + log.Fatalf("Error deleting device: %v", err) } log.Printf("Device %s deleted", deviceID) } From e659e5f9f7e2095ac4d750e169bfa1c7e12116dc Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:21 +0000 Subject: [PATCH 35/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- pkg/buildpkg/buildpkg.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/buildpkg/buildpkg.go b/pkg/buildpkg/buildpkg.go index 18f9f3d9b65..2cec893a2b0 100644 --- a/pkg/buildpkg/buildpkg.go +++ b/pkg/buildpkg/buildpkg.go @@ -21,6 +21,10 @@ const ( // MakeMacOSFatExecutable makes a macOS fat executable from the given binaries. func MakeMacOSFatExecutable(outPath string, inPaths ...string) error { + if len(inPaths) == 0 { + return errors.New("no input files provided") + } + // Read input files. type input struct { data []byte @@ -33,7 +37,7 @@ func MakeMacOSFatExecutable(outPath string, inPaths ...string) error { for _, i := range inPaths { data, err := os.ReadFile(i) if err != nil { - return err + return fmt.Errorf("read input file %s: %w", i, err) } if len(data) < 12 { return fmt.Errorf("file %s too small", i) @@ -63,11 +67,14 @@ func MakeMacOSFatExecutable(outPath string, inPaths ...string) error { // Make output file. out, err := os.Create(outPath) if err != nil { - return err + return fmt.Errorf("create output file %s: %w", outPath, err) } + defer out.Close() + err = out.Chmod(0o755) if err != nil { - return err + os.Remove(outPath) + return fmt.Errorf("chmod output file %s: %w", outPath, err) } // Build a fat_header. @@ -102,7 +109,8 @@ func MakeMacOSFatExecutable(outPath string, inPaths ...string) error { // endianness of the contained files. err = binary.Write(out, binary.BigEndian, hdr) if err != nil { - return err + os.Remove(outPath) + return fmt.Errorf("write fat header to %s: %w", outPath, err) } offset = int64(4 * len(hdr)) @@ -111,19 +119,22 @@ func MakeMacOSFatExecutable(outPath string, inPaths ...string) error { if offset < i.offset { _, err = out.Write(make([]byte, i.offset-offset)) if err != nil { - return err + os.Remove(outPath) + return fmt.Errorf("write padding to %s: %w", outPath, err) } offset = i.offset } _, err := out.Write(i.data) if err != nil { - return err + os.Remove(outPath) + return fmt.Errorf("write input data to %s: %w", outPath, err) } offset += int64(len(i.data)) } err = out.Close() if err != nil { - return err + os.Remove(outPath) + return fmt.Errorf("close output file %s: %w", outPath, err) } return nil From 8807c9abbff559a59020452cd9215b99eca2946f Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:22 +0000 Subject: [PATCH 36/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- tools/luks/luks/main.go | 54 +++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/tools/luks/luks/main.go b/tools/luks/luks/main.go index f8e841c854f..efa63e668b3 100644 --- a/tools/luks/luks/main.go +++ b/tools/luks/luks/main.go @@ -4,9 +4,14 @@ package main import ( "context" + "crypto/rand" + "encoding/base64" "errors" "fmt" + "os" +) +import ( "github.com/fleetdm/fleet/v4/orbit/pkg/dialog" "github.com/fleetdm/fleet/v4/orbit/pkg/lvm" "github.com/fleetdm/fleet/v4/orbit/pkg/zenity" @@ -14,11 +19,13 @@ import ( "github.com/siderolabs/go-blockdevice/v2/encryption/luks" ) -func main() { +const maxKeySlots = 8 +const maxPassphraseRetries = 3 + +func run() error { devicePath, err := lvm.FindRootDisk() if err != nil { - fmt.Println("devicepath err:", err) - panic(err) + return fmt.Errorf("find root disk: %w", err) } prompt := zenity.New() @@ -30,18 +37,24 @@ func main() { HideText: true, }) if err != nil { - fmt.Println("Err ShowEntry") - panic(err) + return fmt.Errorf("show entry dialog: %w", err) } - const escrowPassPhrase = "fleet123" + escrowPassPhrase, err := generateEscrowPassphrase() + if err != nil { + return fmt.Errorf("generate escrow passphrase: %w", err) + } + + // TODO: submit escrowPassPhrase to the secure secret store / server + // rather than only holding it in memory here. device := luks.New(luks.AESXTSPlain64Cipher) keySlot := 1 + passphraseRetries := 0 for { - if keySlot == 8 { - panic(errors.New("all LUKS key slots are full")) + if keySlot >= maxKeySlots { + return errors.New("all LUKS key slots are full") } userKey := encryption.NewKey(0, currentPassphrase) @@ -49,18 +62,23 @@ func main() { if err := device.AddKey(context.Background(), devicePath, userKey, escrowKey); err != nil { if errors.Is(err, encryption.ErrEncryptionKeyRejected) { + passphraseRetries++ + if passphraseRetries > maxPassphraseRetries { + return fmt.Errorf("add key: too many incorrect passphrase attempts: %w", err) + } + currentPassphrase, err = prompt.ShowEntry(dialog.EntryOptions{ Title: "Enter Existing LUKS Passphrase", Text: "Bad password. Enter your existing LUKS passphrase:", HideText: true, }) if err != nil { - fmt.Println("Err Retry ShowEntry") - panic(err) + return fmt.Errorf("show retry entry dialog: %w", err) } continue } + fmt.Println("add key err:", err) keySlot++ continue } @@ -69,4 +87,20 @@ func main() { } fmt.Println("Key escrowed successfully.") + return nil +} + +func generateEscrowPassphrase() (string, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", fmt.Errorf("read random bytes: %w", err) + } + return base64.RawURLEncoding.EncodeToString(buf), nil +} + +func main() { + if err := run(); err != nil { + fmt.Println("luks escrow error:", err) + os.Exit(1) + } } From c777b621c0615eb4ff96c10087d086112890ed15 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:23 +0000 Subject: [PATCH 37/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/service/client_live_query.go | 49 ++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/server/service/client_live_query.go b/server/service/client_live_query.go index 32e64f45335..8b95f1e1669 100644 --- a/server/service/client_live_query.go +++ b/server/service/client_live_query.go @@ -28,8 +28,8 @@ type LiveQueryResultsHandler struct { func NewLiveQueryResultsHandler() *LiveQueryResultsHandler { return &LiveQueryResultsHandler{ - errors: make(chan error), - results: make(chan fleet.DistributedQueryResult), + errors: make(chan error, 1), + results: make(chan fleet.DistributedQueryResult, 1), } } @@ -78,7 +78,7 @@ func (c *Client) LiveQueryWithContext( var responseBody createDistributedQueryCampaignResponse err := c.authenticatedRequest(req, verb, path, &responseBody) if err != nil { - return nil, ctxerr.Errorf(ctx, "create live query: %v", err) + return nil, ctxerr.Errorf(ctx, "create live query: %w", err) } // Copy default dialer but skip cert verification if set. @@ -134,7 +134,11 @@ func (c *Client) LiveQueryWithContext( Data json.RawMessage `json:"data"` }{} - doneReadingChan := make(chan error) + // Buffered so that the reader goroutine below never blocks + // sending its result, even if this goroutine has already + // returned due to ctx.Done(). This avoids a send-on-closed-channel + // panic and avoids leaking the reader goroutine. + doneReadingChan := make(chan error, 1) go func() { doneReadingChan <- conn.ReadJSON(&msg) @@ -145,38 +149,61 @@ func (c *Client) LiveQueryWithContext( return case err := <-doneReadingChan: if err != nil { - resHandler.errors <- ctxerr.Wrap(ctx, err, "receive ws message") + select { + case resHandler.errors <- ctxerr.Wrap(ctx, err, "receive ws message"): + case <-ctx.Done(): + return + } if errors.Is(err, websocket.ErrCloseSent) { return } } } - close(doneReadingChan) switch msg.Type { case "result": var res fleet.DistributedQueryResult if err := json.Unmarshal(msg.Data, &res); err != nil { - resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal results") + select { + case resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal results"): + case <-ctx.Done(): + return + } + } + select { + case resHandler.results <- res: + case <-ctx.Done(): + return } - resHandler.results <- res case "totals": var totals targetTotals if err := json.Unmarshal(msg.Data, &totals); err != nil { - resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal totals") + select { + case resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal totals"): + case <-ctx.Done(): + return + } } resHandler.totals.Store(&totals) case "status": var status campaignStatus if err := json.Unmarshal(msg.Data, &status); err != nil { - resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal status") + select { + case resHandler.errors <- ctxerr.Wrap(ctx, err, "unmarshal status"): + case <-ctx.Done(): + return + } } resHandler.status.Store(&status) default: - resHandler.errors <- ctxerr.Errorf(ctx, "unknown msg type %s", msg.Type) + select { + case resHandler.errors <- ctxerr.Errorf(ctx, "unknown msg type %s", msg.Type): + case <-ctx.Done(): + return + } } } }() From 4d74b307c37093535467e48f427c098d31677a04 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:24 +0000 Subject: [PATCH 38/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- .../mdm/nanomdm/storage/allmulti/allmulti.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/server/mdm/nanomdm/storage/allmulti/allmulti.go b/server/mdm/nanomdm/storage/allmulti/allmulti.go index bb6a9b8389c..6a4d9a6bb64 100644 --- a/server/mdm/nanomdm/storage/allmulti/allmulti.go +++ b/server/mdm/nanomdm/storage/allmulti/allmulti.go @@ -2,6 +2,7 @@ package allmulti import ( "context" + "errors" "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm" "github.com/fleetdm/fleet/v4/server/mdm/nanomdm/storage" @@ -58,6 +59,9 @@ func (ms *MultiAllStorage) execStores(ctx context.Context, r errRunner) (interfa "n", sErr.storeNumber, "err", sErr.err, ) + if finalErr == nil { + finalErr = sErr.err + } } } return finalValue, finalErr @@ -81,7 +85,10 @@ func (ms *MultiAllStorage) RetrieveTokenUpdateTally(ctx context.Context, id stri val, err := ms.execStores(ctx, func(s storage.AllStorage) (interface{}, error) { return s.RetrieveTokenUpdateTally(ctx, id) }) - return val.(int), err + if err != nil { + return 0, err + } + return val.(int), nil } func (ms *MultiAllStorage) StoreUserAuthenticate(r *mdm.Request, msg *mdm.UserAuthenticate) error { @@ -102,17 +109,20 @@ func (ms *MultiAllStorage) ExpandEmbeddedSecrets(ctx context.Context, document s doc, err := ms.execStores(ctx, func(s storage.AllStorage) (interface{}, error) { return s.ExpandEmbeddedSecrets(ctx, document) }) - return doc.(string), err + if err != nil { + return "", err + } + return doc.(string), nil } func (ms *MultiAllStorage) ExpandHostSecrets(ctx context.Context, document string, enrollmentID string) (string, error) { // NOT IMPLEMENTED - return document, nil + return document, errors.New("not implemented") } func (ms *MultiAllStorage) SetRecoveryLockFailed(ctx context.Context, hostUUID string, errorMsg string) error { // NOT IMPLEMENTED - return nil + return errors.New("not implemented") } func (ms *MultiAllStorage) BulkDeleteHostUserCommandsWithoutResults(ctx context.Context, commandToIDs map[string][]string) error { From 08d96b9c51c2d8460e560a6a94773399df3fee36 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:25 +0000 Subject: [PATCH 39/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/vulnerabilities/nvd/sync/cve_syncer.go | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/server/vulnerabilities/nvd/sync/cve_syncer.go b/server/vulnerabilities/nvd/sync/cve_syncer.go index ecbc4226611..d310820d6b0 100644 --- a/server/vulnerabilities/nvd/sync/cve_syncer.go +++ b/server/vulnerabilities/nvd/sync/cve_syncer.go @@ -21,6 +21,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "github.com/fleetdm/fleet/v4/orbit/pkg/constant" @@ -45,6 +46,12 @@ type CVE struct { debug bool WaitTimeForRetry time.Duration MaxTryAttempts int + + // cachedCVEFeedsMu guards cachedCVEFeeds below. + cachedCVEFeedsMu sync.Mutex + // cachedCVEFeeds caches per-year legacy CVE feeds for this instance's dbDir + // while a VulnCheck sync is in progress, to avoid repeated file reads/writes. + cachedCVEFeeds map[int]*schema.NVDCVEFeedJSON10 } var ( @@ -92,6 +99,7 @@ func NewCVE(dbDir string, opts ...CVEOption) (*CVE, error) { logger: slog.New(slog.DiscardHandler), MaxTryAttempts: maxRetryAttempts, WaitTimeForRetry: waitTimeForRetry, + cachedCVEFeeds: map[int]*schema.NVDCVEFeedJSON10{}, } for _, fn := range opts { fn(&s) @@ -201,12 +209,19 @@ func (s *CVE) update(ctx context.Context) error { return nil } -func (s *CVE) updateYearFile(ctx context.Context, year int, cves []nvdapi.CVEItem) error { +// legacyFeedYear clamps the given year to the earliest year supported by the +// legacy NVD feed format used by the github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools +// package (and originally the facebookincubator/nvdtools package it was forked from). +func legacyFeedYear(year int) int { // The NVD legacy feed files start at year 2002. - // This is assumed by the github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools package. if year < 2002 { - year = 2002 + return 2002 } + return year +} + +func (s *CVE) updateYearFile(ctx context.Context, year int, cves []nvdapi.CVEItem) error { + year = legacyFeedYear(year) // Read the CVE file for the year. readStart := time.Now() @@ -257,20 +272,17 @@ func (s *CVE) updateYearFile(ctx context.Context, year int, cves []nvdapi.CVEIte return nil } -var cachedCVEFeeds = map[int]*schema.NVDCVEFeedJSON10{} - func (s *CVE) updateVulnCheckYearFile(ctx context.Context, year int, cves []VulnCheckCVE, modCount, addCount *int) error { - // The NVD legacy feed files start at year 2002. - // This is assumed by the facebookincubator/nvdtools package. - if year < 2002 { - year = 2002 - } + year = legacyFeedYear(year) updateStart := time.Now() + s.cachedCVEFeedsMu.Lock() + defer s.cachedCVEFeedsMu.Unlock() + var storedCVEFeed *schema.NVDCVEFeedJSON10 var err error - if feed, ok := cachedCVEFeeds[year]; ok && feed != nil { + if feed, ok := s.cachedCVEFeeds[year]; ok && feed != nil { storedCVEFeed = feed } else { storedCVEFeed, err = readCVEsLegacyFormat(s.dbDir, year) @@ -325,7 +337,7 @@ func (s *CVE) updateVulnCheckYearFile(ctx context.Context, year int, cves []Vuln storedCVEFeed.CVEDataNumberOfCVEs = strconv.FormatInt(int64(len(storedCVEFeed.CVEItems)), 10) // Store the file for the year. - cachedCVEFeeds[year] = storedCVEFeed + s.cachedCVEFeeds[year] = storedCVEFeed return nil } @@ -333,7 +345,7 @@ func (s *CVE) updateVulnCheckYearFile(ctx context.Context, year int, cves []Vuln func (s *CVE) writeLastModStartDateFile(lastModStartDate string) error { normalized, err := parseAndFormatForNVD(lastModStartDate) if err != nil { - return err + return fmt.Errorf("writeLastModStartDateFile: %w", err) } return os.WriteFile( @@ -703,7 +715,9 @@ func (s *CVE) processVulnCheckFile(ctx context.Context, fileName string) error { return zipReader.File[i].Name > zipReader.File[j].Name }) - cachedCVEFeeds = map[int]*schema.NVDCVEFeedJSON10{} // clear feeds cache for consistency + s.cachedCVEFeedsMu.Lock() + s.cachedCVEFeeds = map[int]*schema.NVDCVEFeedJSON10{} // clear feeds cache for consistency + s.cachedCVEFeedsMu.Unlock() // files are in reverse chronological order by modification date // so we can stop processing files once we find one that is older @@ -765,6 +779,9 @@ func (s *CVE) processVulnCheckFile(ctx context.Context, fileName string) error { // only save updated files post-vulncheck-hydration storeStart := time.Now() + s.cachedCVEFeedsMu.Lock() + cachedCVEFeeds := s.cachedCVEFeeds + s.cachedCVEFeedsMu.Unlock() for year, storedCVEFeed := range cachedCVEFeeds { if err := storeCVEsInLegacyFormat(s.dbDir, year, storedCVEFeed); err != nil { return err From fa3ef2d90560475a173b89cb4cdb441a272bf4ed Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:49:26 +0000 Subject: [PATCH 40/40] fix(adhoc-sweep-fixes): 66 review findings across 40 files --- server/vulnerabilities/nvd/tools/providers/nvd/cpe.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/vulnerabilities/nvd/tools/providers/nvd/cpe.go b/server/vulnerabilities/nvd/tools/providers/nvd/cpe.go index def9db3c06c..61bbb4f35d0 100644 --- a/server/vulnerabilities/nvd/tools/providers/nvd/cpe.go +++ b/server/vulnerabilities/nvd/tools/providers/nvd/cpe.go @@ -168,6 +168,10 @@ func (cf cpeFile) Sync(ctx context.Context, src SourceConfig, localdir string) e func (cf cpeFile) needsUpdate(ctx context.Context, targetURL, localdir string) (bool, error) { flog.V(1).Infof("checking etag for %q", targetURL) + if _, err := os.Stat(filepath.Join(localdir, cf.DataFile)); err != nil { + flog.V(1).Infof("data file %q does not exist in %q, needs sync", cf.DataFile, localdir) + return true, nil + } req, err := httpNewRequestContext(ctx, "HEAD", targetURL) if err != nil { return false, err @@ -216,6 +220,7 @@ func (cf cpeFile) download(ctx context.Context, targetURL string) (string, strin if err != nil { return "", "", err } + defer dataFile.Close() _, err = io.Copy(dataFile, resp.Body) if err != nil { return "", "", err