From a4d523d2bd7acba3dbc754419efce713611592ae Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:32 +0000 Subject: [PATCH 01/30] fix(FLEETMDM-001): 48 review findings across 30 files --- ...1154558_ChangeCiscoSecureClientBundleId.go | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20251031154558_ChangeCiscoSecureClientBundleId.go b/server/datastore/mysql/migrations/tables/20251031154558_ChangeCiscoSecureClientBundleId.go index 6c6bbed1f6f..b35ee6f15bb 100644 --- a/server/datastore/mysql/migrations/tables/20251031154558_ChangeCiscoSecureClientBundleId.go +++ b/server/datastore/mysql/migrations/tables/20251031154558_ChangeCiscoSecureClientBundleId.go @@ -1,3 +1,4 @@ +// >>> OPENFRAME(cisco-secure-client-bundle-id): Fork-specific migration to fix Cisco Secure Client bundle-id — openframe/docs/cisco-secure-client-bundle-id.md package tables import ( @@ -22,7 +23,7 @@ func Up_20251031154558(tx *sql.Tx) error { WHERE bundle_identifier IN ('com.cisco.pkg.anyconnect.vpn', 'com.cisco.secureclient.gui') `) if err != nil { - return err + return fmt.Errorf("querying software_titles for cisco bundle ids: %w", err) } defer titleRows.Close() @@ -30,12 +31,12 @@ func Up_20251031154558(tx *sql.Tx) error { for titleRows.Next() { var id, bundleIdentifier string if err := titleRows.Scan(&id, &bundleIdentifier); err != nil { - return err + return fmt.Errorf("scanning software_titles row: %w", err) } bundleIdToTitleId[bundleIdentifier] = id } if err := titleRows.Err(); err != nil { - return err + return fmt.Errorf("iterating software_titles rows: %w", err) } if len(bundleIdToTitleId) == 0 { @@ -51,12 +52,12 @@ func Up_20251031154558(tx *sql.Tx) error { ('Cisco Secure Client', 'apps', 'com.cisco.secureclient.gui') `) if err != nil { - return err + return fmt.Errorf("inserting correct cisco secure client software title: %w", err) } lastInsertId, err := res.LastInsertId() if err != nil { - return err + return fmt.Errorf("getting last insert id for cisco secure client software title: %w", err) } bundleIdToTitleId["com.cisco.secureclient.gui"] = fmt.Sprintf("%d", lastInsertId) } @@ -66,10 +67,9 @@ func Up_20251031154558(tx *sql.Tx) error { SELECT id FROM software_installers WHERE title_id = ? - AND extension = 'pkg' `, bundleIdToTitleId["com.cisco.pkg.anyconnect.vpn"]) if err != nil { - return err + return fmt.Errorf("querying software_installers with incorrect cisco title id: %w", err) } defer installerRows.Close() @@ -77,12 +77,12 @@ func Up_20251031154558(tx *sql.Tx) error { for installerRows.Next() { var id string if err := installerRows.Scan(&id); err != nil { - return err + return fmt.Errorf("scanning software_installers row: %w", err) } softwareInstallerIds = append(softwareInstallerIds, id) } if err := installerRows.Err(); err != nil { - return err + return fmt.Errorf("iterating software_installers rows: %w", err) } // Update software installers to point to correct title @@ -92,7 +92,7 @@ func Up_20251031154558(tx *sql.Tx) error { SET title_id = ? WHERE id = ? `, bundleIdToTitleId["com.cisco.secureclient.gui"], softwareInstallerId); err != nil { - return err + return fmt.Errorf("updating software_installers title_id for id %s: %w", softwareInstallerId, err) } } @@ -102,11 +102,12 @@ func Up_20251031154558(tx *sql.Tx) error { DELETE FROM software_titles WHERE id = ? `, incorrectTitleId); err != nil { - return err + return fmt.Errorf("deleting incorrect cisco software title id %s: %w", incorrectTitleId, err) } } return nil } +// <<< OPENFRAME(cisco-secure-client-bundle-id) func Down_20251031154558(tx *sql.Tx) error { return nil From 2f87a639c8d784155cfac1d9997e7363b1f76ed5 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:34 +0000 Subject: [PATCH 02/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/datastore/redis/keyprefix.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/server/datastore/redis/keyprefix.go b/server/datastore/redis/keyprefix.go index c606506d150..6218b2a8507 100644 --- a/server/datastore/redis/keyprefix.go +++ b/server/datastore/redis/keyprefix.go @@ -1,5 +1,12 @@ package redis +// OPENFRAME BEGIN: fork-specific multi-tenant key-prefixing wrapper. +// This entire file is fork-only logic added on top of the shared upstream +// fleetdm/fleet server/datastore/redis package. It implements Redis key/ +// channel prefixing for multi-tenant isolation and does not exist upstream. +// On upstream syncs of this package, this file must be preserved as-is; +// nothing here should be merged/overwritten from upstream. + import ( "fmt" "strings" @@ -170,6 +177,11 @@ func bitopArgs(args []interface{}, prefix string) { } // objectArgs: OBJECT ENCODING/IDLETIME/FREQ/REFCOUNT key — key at args[1]. +// For any other/unrecognized subcommand (e.g. HELP, or a future subcommand +// this list doesn't know about) we fail closed and prefix args[1] anyway if +// present, since silently forwarding an unprefixed potential key argument +// risks cross-tenant key access. HELP takes no key argument, so prefixing an +// absent/irrelevant arg[1] is a no-op in practice for that case. func objectArgs(args []interface{}, prefix string) { if len(args) < 2 { return @@ -178,10 +190,15 @@ func objectArgs(args []interface{}, prefix string) { switch sub { case "ENCODING", "IDLETIME", "FREQ", "REFCOUNT": prefixOne(args, 1, prefix) + default: + prefixOne(args, 1, prefix) } } -// pubsubArgs: only NUMSUB/CHANNELS/SHARD* take channel args (at args[1..]). +// pubsubArgs: NUMSUB/CHANNELS/SHARD* take channel args (at args[1..]). For +// any other/unrecognized subcommand, fail closed by prefixing all remaining +// args as channels too, since forwarding them unprefixed risks cross-tenant +// channel leakage if a future subcommand also takes channel arguments. func pubsubArgs(args []interface{}, prefix string) { if len(args) < 1 { return @@ -192,6 +209,10 @@ func pubsubArgs(args []interface{}, prefix string) { for i := 1; i < len(args); i++ { prefixOne(args, i, prefix) } + default: + for i := 1; i < len(args); i++ { + prefixOne(args, i, prefix) + } } } @@ -358,3 +379,5 @@ var specialCmds = map[string]prefixRule{ "BZPOPMIN": blockingKeysArgs, "BZPOPMAX": blockingKeysArgs, } + +// OPENFRAME END: fork-specific multi-tenant key-prefixing wrapper. From d58eb191b0891979cdddf33a102148095dd72075 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:35 +0000 Subject: [PATCH 03/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/mdm/maintainedapps/sync.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/server/mdm/maintainedapps/sync.go b/server/mdm/maintainedapps/sync.go index 6f46b37fa0f..cf7238902eb 100644 --- a/server/mdm/maintainedapps/sync.go +++ b/server/mdm/maintainedapps/sync.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -29,6 +28,7 @@ type AppsList struct { Apps []appListing `json:"apps"` } +// >>> OPENFRAME(fma-cdn-fallback): fork-specific primary/fallback CDN fetch for FMA manifests — openframe/docs/fma-cdn-fallback.md const fmaOutputsBase = "https://maintained-apps.fleetdm.com/manifests" const fmaOutputsFallbackBase = "https://raw.githubusercontent.com/fleetdm/fleet/refs/heads/main/ee/maintained-apps/outputs" @@ -93,7 +93,7 @@ func doFetch(ctx context.Context, baseURL, path string) ([]byte, error) { case http.StatusOK: return body, nil case http.StatusNotFound: - return nil, errors.New("not found (HTTP 404)") + return nil, ctxerr.New(ctx, "not found (HTTP 404)") default: if len(body) > 512 { body = body[:512] @@ -102,6 +102,8 @@ func doFetch(ctx context.Context, baseURL, path string) ([]byte, error) { } } +// <<< OPENFRAME(fma-cdn-fallback) + // SyncAppsList fetches the latest FMA apps list and updates the apps list copy cached in the DB func SyncAppsList(ctx context.Context, ds fleet.Datastore) error { appsList, err := FetchAppsList(ctx) @@ -113,10 +115,12 @@ func SyncAppsList(ctx context.Context, ds fleet.Datastore) error { } func FetchAppsList(ctx context.Context) (*AppsList, error) { + // >>> OPENFRAME(fma-cdn-fallback): fetch via primary/fallback CDN helper instead of upstream single-CDN fetch — openframe/docs/fma-cdn-fallback.md body, err := fetchManifestFile(ctx, "/apps.json") if err != nil { return nil, ctxerr.Wrap(ctx, err, "fetch apps list") } + // <<< OPENFRAME(fma-cdn-fallback) var appsList AppsList if err := json.Unmarshal(body, &appsList); err != nil { @@ -125,6 +129,13 @@ func FetchAppsList(ctx context.Context) (*AppsList, error) { if appsList.Version != 2 { return nil, ctxerr.New(ctx, "apps list is an incompatible version") } + + for i := range appsList.Apps { + if appsList.Apps[i].UniqueIdentifier == "" { + appsList.Apps[i].UniqueIdentifier = appsList.Apps[i].Name + } + } + return &appsList, nil } @@ -204,10 +215,12 @@ func Hydrate(ctx context.Context, app *fleet.MaintainedApp, version string, team return app, nil } + // >>> OPENFRAME(fma-cdn-fallback): fetch via primary/fallback CDN helper instead of upstream single-CDN fetch — openframe/docs/fma-cdn-fallback.md body, err := fetchManifestFile(ctx, fmt.Sprintf("/%s.json", app.Slug)) if err != nil { return nil, ctxerr.Wrap(ctx, err, "fetch app manifest") } + // <<< OPENFRAME(fma-cdn-fallback) var manifest ma.FMAManifestFile if err := json.Unmarshal(body, &manifest); err != nil { From 3faa94a633746b98abdb96131c25f8d82fadf73c Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:36 +0000 Subject: [PATCH 04/30] fix(FLEETMDM-001): 48 review findings across 30 files --- .../platform/endpointer/json_key_rewriter.go | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/server/platform/endpointer/json_key_rewriter.go b/server/platform/endpointer/json_key_rewriter.go index 0fee6783473..beb48f7dd42 100644 --- a/server/platform/endpointer/json_key_rewriter.go +++ b/server/platform/endpointer/json_key_rewriter.go @@ -1,3 +1,9 @@ +// >>> OPENFRAME(fleet-id-rename): This entire file implements OpenFrame's +// fork-specific team_id→fleet_id (and related) JSON key aliasing mechanism. +// It is fork-only business logic layered on top of the shared Fleet API +// surface and does not exist upstream. When syncing from fleetdm/fleet, +// preserve this file as-is unless the fork's team/fleet renaming strategy +// changes. See FLEETMDM-001 for the sentinel convention. package endpointer import ( @@ -54,6 +60,11 @@ type JSONKeyRewriteReader struct { reader *bytes.Reader initErr error + // initDone is set once construction (rewrite) has completed, whether it + // succeeded or failed. It guards UsedDeprecatedKeys against being called + // before the rewrite has actually run. + initDone bool + // Map from old (deprecated) key to its AliasRule for fast lookup. oldKeyIndex map[string]AliasRule // Map from new key to its AliasRule for fast lookup. @@ -84,9 +95,11 @@ func NewJSONKeyRewriteReader(src io.Reader, rules []AliasRule) *JSONKeyRewriteRe var buf bytes.Buffer if err := rw.rewrite(src, &buf); err != nil { rw.initErr = err + rw.initDone = true return rw } rw.reader = bytes.NewReader(buf.Bytes()) + rw.initDone = true return rw } @@ -94,7 +107,15 @@ func NewJSONKeyRewriteReader(src io.Reader, rules []AliasRule) *JSONKeyRewriteRe // encountered during reading. This should be called after the reader has been // fully consumed (i.e., after json.Decoder.Decode or similar has returned), // which guarantees the background goroutine has finished. +// +// If construction failed (see initErr) or has not completed yet, or if the +// reader has not been fully read via Read, calling this returns an empty +// slice rather than a misleading partial result; callers must check the +// error returned by Read before relying on this method's output. func (r *JSONKeyRewriteReader) UsedDeprecatedKeys() []string { + if !r.initDone || r.initErr != nil { + return []string{} + } keys := make([]string, 0, len(r.usedDeprecated)) for k := range r.usedDeprecated { keys = append(keys, k) @@ -176,6 +197,14 @@ func RewriteOldToNewKeys(data []byte, rules []AliasRule) ([]byte, error) { // install flag on those items collides with the `macos_setup`↔`setup_experience` // rename on the MDM section, so renames are skipped under this subtree. See // https://github.com/fleetdm/fleet/issues/44970. +// +// NOTE: this is a path-independent heuristic — it matches the literal key +// name "software" anywhere in the document, not a specific structural +// position (e.g. TeamSpec.Software). If a future payload introduces an +// unrelated top-level "software" key, renames would be incorrectly suppressed +// within that subtree too. A more robust fix would track the full key path +// rather than a single depth counter; that is a larger structural change and +// is not made here. const softwareScopeKey = "software" // rewrite reads tokens from src, rewrites deprecated keys, checks for alias @@ -344,3 +373,7 @@ func (r *JSONKeyRewriteReader) rewrite(src io.Reader, w io.Writer) error { } } } + +// >>> OPENFRAME(fleet-id-rename): end of fork-specific JSON key aliasing +// mechanism. +// <<< OPENFRAME(fleet-id-rename) From ae704c80884f891f8f9a60df50b456196bad1308 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:37 +0000 Subject: [PATCH 05/30] fix(FLEETMDM-001): 48 review findings across 30 files --- .../openframe/openframe-encryption-service.go | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/server/service/openframe/openframe-encryption-service.go b/server/service/openframe/openframe-encryption-service.go index 5029dfb41a2..d318ca28dfd 100644 --- a/server/service/openframe/openframe-encryption-service.go +++ b/server/service/openframe/openframe-encryption-service.go @@ -9,6 +9,10 @@ import ( "github.com/rs/zerolog/log" ) +// openframeTokenRefreshErrorLogInterval controls how often (in number of +// consecutive errors) decrypt failures are logged, to avoid flooding logs. +const openframeTokenRefreshErrorLogInterval = 100 + type OpenframeEncryptionService struct { encryptionKey string decryptErrCount int @@ -27,7 +31,16 @@ func (es *OpenframeEncryptionService) Decrypt(data string) ([]byte, error) { if es.decryptErrCount % openframeTokenRefreshErrorLogInterval == 1 { log.Error().Err(err).Msg("Error decoding base64 data") } - return nil, err + return nil, fmt.Errorf("decode base64 data: %w", err) + } + + keyLen := len(es.encryptionKey) + if keyLen != 16 && keyLen != 24 && keyLen != 32 { + es.decryptErrCount++ + if es.decryptErrCount % openframeTokenRefreshErrorLogInterval == 1 { + log.Error().Int("key_length", keyLen).Msg("Invalid AES key length") + } + return nil, fmt.Errorf("invalid AES key length %d: must be 16, 24, or 32 bytes", keyLen) } block, err := aes.NewCipher([]byte(es.encryptionKey)) @@ -36,16 +49,16 @@ func (es *OpenframeEncryptionService) Decrypt(data string) ([]byte, error) { if es.decryptErrCount % openframeTokenRefreshErrorLogInterval == 1 { log.Error().Err(err).Msg("Error creating cipher") } - return nil, err + return nil, fmt.Errorf("create cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { - return nil, err + return nil, fmt.Errorf("create gcm: %w", err) } if len(encryptedData) < gcm.NonceSize() { - return nil, fmt.Errorf("ciphertext too short") + return nil, fmt.Errorf("decrypt: ciphertext too short") } nonce := encryptedData[:gcm.NonceSize()] @@ -57,7 +70,7 @@ func (es *OpenframeEncryptionService) Decrypt(data string) ([]byte, error) { if es.decryptErrCount % openframeTokenRefreshErrorLogInterval == 1 { log.Error().Err(err).Msg("Error decrypting data") } - return nil, err + return nil, fmt.Errorf("gcm open: %w", err) } es.decryptErrCount = 0 From 607c97136ff0eb29a94981909e899356271ae394 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:39 +0000 Subject: [PATCH 06/30] fix(FLEETMDM-001): 48 review findings across 30 files --- ee/orbit/pkg/hostidentity/host_identity.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ee/orbit/pkg/hostidentity/host_identity.go b/ee/orbit/pkg/hostidentity/host_identity.go index 9ff45a33df5..e023021f53f 100644 --- a/ee/orbit/pkg/hostidentity/host_identity.go +++ b/ee/orbit/pkg/hostidentity/host_identity.go @@ -96,6 +96,9 @@ func Setup( return nil, fmt.Errorf("failed to load secure hardware key: %w", err) } + // >>> OPENFRAME(host-identity-renewal): Certificate renewal on approaching expiry and + // scheduled restart timer are fork-specific extensions to the upstream SCEP issuance flow. + // See openframe/docs/host-identity-renewal.md clientCert, err := loadSCEPClientCert(metadataDir) switch { case err == nil && certNeedsRenewal(clientCert, certificateRenewalThreshold): @@ -111,6 +114,7 @@ func Setup( clientCert = renewedCert logger.Info().Msg("Certificate renewal completed successfully") } + // <<< OPENFRAME(host-identity-renewal) case errors.Is(err, os.ErrNotExist): // We don't have a certificate, let's issue one using SCEP. opts := []scep.Option{ @@ -164,6 +168,8 @@ func Setup( } logger.Debug().Msg("secure HW key matches certificate public key") + // >>> OPENFRAME(host-identity-renewal): scheduled restart timer for certificate renewal. + // See openframe/docs/host-identity-renewal.md // Start a goroutine with a timer to trigger restart for certificate renewal if restartFunc != nil { go func() { @@ -193,6 +199,7 @@ func Setup( restartFunc("host identity certificate renewal") }() } + // <<< OPENFRAME(host-identity-renewal) return credentials, nil } @@ -226,6 +233,10 @@ func saveSCEPClientCert(metadataDir string, cert *x509.Certificate) error { return nil } +// >>> OPENFRAME(host-identity-renewal): certificate renewal helper functions are +// fork-specific extensions on top of the upstream SCEP issuance flow. +// See openframe/docs/host-identity-renewal.md + // certNeedsRenewal checks if the certificate expires within the given duration func certNeedsRenewal(cert *x509.Certificate, renewalThreshold time.Duration) bool { return time.Until(cert.NotAfter) < renewalThreshold @@ -261,8 +272,10 @@ func RenewCertificate( // Ensure we restore the backup if something goes wrong, like we cannot connect to Fleet server to get a cert defer func() { - if _, err := os.Stat(oldKeyPath); err == nil { - _ = os.Rename(oldKeyPath, keyPath) + if _, statErr := os.Stat(oldKeyPath); statErr == nil { + if renameErr := os.Rename(oldKeyPath, keyPath); renameErr != nil { + logger.Error().Err(renameErr).Msg("failed to restore key backup after failed certificate renewal; host may be left without a usable key") + } } }() @@ -358,3 +371,5 @@ func fetchCertWithRenewal( // Fetch the certificate with the renewal extension in the CSR return scepClient.FetchCert(ctx) } + +// <<< OPENFRAME(host-identity-renewal) From a80e99bf32b5abb23b74585a225b882722076556 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:41 +0000 Subject: [PATCH 07/30] fix(FLEETMDM-001): 48 review findings across 30 files --- ...626000001_ScopeHostIdentityUniqueToTeam.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go b/server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go index 4b49e5c69f7..e2981a79cff 100644 --- a/server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go +++ b/server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go @@ -1,3 +1,7 @@ +// >>> OPENFRAME(host-identity-team-scope): scopes the upstream hosts table's +// osquery_host_id uniqueness constraint per-team instead of globally, so the +// same device can enroll into more than one tenant team under shared-database +// multitenancy — openframe/docs/upstream-sync-conflict-resolution.md package openframe import ( @@ -95,6 +99,51 @@ func Up_20260626000001(tx *sql.Tx) error { return nil } +// Down_20260626000001 restores the pre-migration schema: it re-creates the +// original global UNIQUE(osquery_host_id) index and drops the per-team unique +// index and its supporting generated column. This is only safe to run if no +// rows currently violate a global-unique(osquery_host_id) constraint (i.e., no +// device has actually been enrolled into more than one team since Up ran); if +// such rows exist, re-adding idx_osquery_host_id will fail with a duplicate-key +// error, which is the correct, safe failure mode for an unsound rollback. func Down_20260626000001(tx *sql.Tx) error { + const ( + table = "hosts" + oldIndex = "idx_osquery_host_id" + newIndex = "idx_hosts_team_osquery_host_id" + genColumn = "openframe_team_key" + ) + + hasOld, err := indexExists(tx, table, oldIndex) + if err != nil { + return fmt.Errorf("checking %s index: %w", oldIndex, err) + } + if !hasOld { + if _, err := tx.Exec("ALTER TABLE hosts ADD UNIQUE KEY idx_osquery_host_id (osquery_host_id)"); err != nil { + return fmt.Errorf("adding %s unique index: %w", oldIndex, err) + } + } + + hasNew, err := indexExists(tx, table, newIndex) + if err != nil { + return fmt.Errorf("checking %s index: %w", newIndex, err) + } + if hasNew { + if _, err := tx.Exec("ALTER TABLE hosts DROP INDEX idx_hosts_team_osquery_host_id"); err != nil { + return fmt.Errorf("dropping %s index: %w", newIndex, err) + } + } + + hasCol, err := columnExists(tx, table, genColumn) + if err != nil { + return fmt.Errorf("checking %s column: %w", genColumn, err) + } + if hasCol { + if _, err := tx.Exec("ALTER TABLE hosts DROP COLUMN openframe_team_key"); err != nil { + return fmt.Errorf("dropping %s column: %w", genColumn, err) + } + } return nil } + +// <<< OPENFRAME(host-identity-team-scope) From aa036dea5abc8e876a862c9a3ec26c81e9d74ed9 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:42 +0000 Subject: [PATCH 08/30] fix(FLEETMDM-001): 48 review findings across 30 files --- .../tables/20260409153713_AddNameToNanoCommands.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go b/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go index ecb4eb844db..cf8826a9fd8 100644 --- a/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go +++ b/server/datastore/mysql/migrations/tables/20260409153713_AddNameToNanoCommands.go @@ -9,6 +9,7 @@ func init() { MigrationClient.AddMigration(Up_20260409153713, Down_20260409153713) } +// >>> OPENFRAME(nano-commands-name) func Up_20260409153713(tx *sql.Tx) error { if !columnExists(tx, "nano_commands", "name") { _, err := tx.Exec(` @@ -18,8 +19,15 @@ ALTER TABLE nano_commands ADD COLUMN name varchar(255) CHARACTER SET utf8mb4 COL } } - // Recreate the view to include the new name column + // Backfill existing rows so name is not silently left NULL for historical commands. _, err := tx.Exec(` +UPDATE nano_commands SET name = request_type WHERE name IS NULL`) + if err != nil { + return fmt.Errorf("failed to backfill nano_commands.name column: %w", err) + } + + // Recreate the view to include the new name column + _, err = tx.Exec(` CREATE OR REPLACE SQL SECURITY INVOKER VIEW nano_view_queue AS SELECT q.id COLLATE utf8mb4_unicode_ci AS id, @@ -55,3 +63,5 @@ ORDER BY func Down_20260409153713(_ *sql.Tx) error { return nil } + +// <<< OPENFRAME(nano-commands-name) From 4c4e81302ba2f29a82e7ea890aa6021020f000c0 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:43 +0000 Subject: [PATCH 09/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/mail/mfa.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/mail/mfa.go b/server/mail/mfa.go index c2759e02a46..a9b0d17ac4f 100644 --- a/server/mail/mfa.go +++ b/server/mail/mfa.go @@ -1,7 +1,10 @@ package mail +// >>> OPENFRAME(mfa-mailer): MFA email support is a fork-specific addition not present in upstream fleetdm/fleet — openframe/docs/mfa.md + import ( "bytes" + "fmt" "github.com/fleetdm/fleet/v4/server/fleet" "html/template" "time" @@ -24,12 +27,14 @@ func (i *MFAMailer) Message() ([]byte, error) { i.TTLInMinutes = fleet.MFALinkTTL.Truncate(time.Minute).Minutes() // better to show a whole, rounded-down number t, err := server.GetTemplate("server/mail/templates/mfa.html", "email_template") if err != nil { - return nil, err + return nil, fmt.Errorf("get mfa email template: %w", err) } var msg bytes.Buffer if err = t.Execute(&msg, i); err != nil { - return nil, err + return nil, fmt.Errorf("execute mfa email template: %w", err) } return msg.Bytes(), nil } + +// <<< OPENFRAME(mfa-mailer) From ce0986a38cf9b0971e4973ff80b3817e924da9f1 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:45 +0000 Subject: [PATCH 10/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/datastore/mysqlredis/hosts.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/datastore/mysqlredis/hosts.go b/server/datastore/mysqlredis/hosts.go index a4bea4c8842..d9b10435dfa 100644 --- a/server/datastore/mysqlredis/hosts.go +++ b/server/datastore/mysqlredis/hosts.go @@ -1,3 +1,11 @@ +// >>> OPENFRAME(mysqlredis-hosts): fork-only shim wrapping fleet.Datastore host +// write methods (NewHost, EnrollOsquery, DeleteHost, DeleteHosts, +// CleanupExpiredHosts, CleanupIncomingHosts, CanEnrollNewHost) with Redis-backed +// enrolled-host-count enforcement and cache invalidation. Must be kept in sync +// with upstream fleetdm/fleet's fleet.Datastore interface: any change to the +// signatures or semantics of the wrapped methods upstream requires review here. +// <<< OPENFRAME(mysqlredis-hosts) + package mysqlredis import ( @@ -131,6 +139,7 @@ func (d *Datastore) NewHost(ctx context.Context, host *fleet.Host) (*fleet.Host, } if d.enforceHostLimit > 0 { if err := addHosts(ctx, d.pool, h.ID); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: add host after NewHost")) logging.WithErr(ctx, err) } } @@ -150,6 +159,7 @@ func (d *Datastore) EnrollOsquery(ctx context.Context, opts ...fleet.DatastoreEn } if d.enforceHostLimit > 0 { if err := addHosts(ctx, d.pool, h.ID); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: add host after EnrollOsquery")) logging.WithErr(ctx, err) } } @@ -166,6 +176,7 @@ func (d *Datastore) DeleteHost(ctx context.Context, hid uint) error { } if d.enforceHostLimit > 0 { if err := removeHosts(ctx, d.pool, hid); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: remove host after DeleteHost")) logging.WithErr(ctx, err) } } @@ -182,6 +193,7 @@ func (d *Datastore) DeleteHosts(ctx context.Context, ids []uint) error { } if d.enforceHostLimit > 0 { if err := removeHosts(ctx, d.pool, ids...); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: remove hosts after DeleteHosts")) logging.WithErr(ctx, err) } } @@ -201,6 +213,7 @@ func (d *Datastore) CleanupExpiredHosts(ctx context.Context) ([]fleet.DeletedHos } if d.enforceHostLimit > 0 { if err := removeHosts(ctx, d.pool, ids...); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: remove hosts after CleanupExpiredHosts")) logging.WithErr(ctx, err) } } @@ -215,6 +228,7 @@ func (d *Datastore) CleanupIncomingHosts(ctx context.Context, now time.Time) ([] } if d.enforceHostLimit > 0 { if err := removeHosts(ctx, d.pool, ids...); err != nil { + ctxerr.Handle(ctx, ctxerr.Wrap(ctx, err, "enrolled limits: remove hosts after CleanupIncomingHosts")) logging.WithErr(ctx, err) } } From 9248509034f3fdaa808604f9d5156a2c9621e3c5 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:46 +0000 Subject: [PATCH 11/30] fix(FLEETMDM-001): 48 review findings across 30 files --- charts/fleet/templates/job-migration.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/charts/fleet/templates/job-migration.yaml b/charts/fleet/templates/job-migration.yaml index c3c1ebfde3e..4c83a8af7b2 100644 --- a/charts/fleet/templates/job-migration.yaml +++ b/charts/fleet/templates/job-migration.yaml @@ -229,7 +229,10 @@ spec: {{- if .Values.database.tls.enabled }} - name: mysql-tls secret: - # >>> OPENFRAME(helm): the server CA rides in the same externally managed Secret as + # >>> OPENFRAME(helm): the server CA, client cert and client key all live in the + # same externally managed Secret referenced by database.existingSecret (or the + # chart-managed database.secretName fallback), so this volume mounts that single + # Secret and FLEET_MYSQL_TLS_CA/CERT/KEY above select the individual keys from it. secretName: "{{ default .Values.database.secretName .Values.database.existingSecret }}" # <<< OPENFRAME(helm) {{- end }} From 0a3c67bc338cce511f439792b8b1d8a93b085c4b Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:48 +0000 Subject: [PATCH 12/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/service/microsoft_mdm_integration_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/service/microsoft_mdm_integration_test.go b/server/service/microsoft_mdm_integration_test.go index 1db3c40ed16..2de815d90a0 100644 --- a/server/service/microsoft_mdm_integration_test.go +++ b/server/service/microsoft_mdm_integration_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" ) +// >>> OPENFRAME(windows-mdm-reconcile-tests): fork-specific Windows MDM profile reconcile test helpers and tests // enrollWindowsHostInMDMForTest inserts a Windows MDM enrollment for the host and mirrors what osquery's // directIngestMDMWindows does (host_mdm.enrolled = 1 once the device's registry confirms MDM enrollment), making the // host eligible for the Windows profile reconcilers. Returns the enrolled device row, reloaded so its ID is set. @@ -163,3 +164,5 @@ func TestReconcileWindowsProfilesForEnrollingHost(t *testing.T) { require.NoError(t, err) require.Len(t, cmdsAgain, 1, "second per-host reconcile run must not enqueue another command") } + +// <<< OPENFRAME(windows-mdm-reconcile-tests) From 642e290f5f8be3e765aecbc4cbe8f0db17d1d8f0 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:49 +0000 Subject: [PATCH 13/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/datastore/mysql/campaigns.go | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/server/datastore/mysql/campaigns.go b/server/datastore/mysql/campaigns.go index 32752f05a32..6399d1ae0fc 100644 --- a/server/datastore/mysql/campaigns.go +++ b/server/datastore/mysql/campaigns.go @@ -88,7 +88,16 @@ func (ds *Datastore) SaveDistributedQueryCampaign(ctx context.Context, camp *fle user_id = ? WHERE id = ? ` - result, err := ds.writer(ctx).ExecContext(ctx, sqlStatement, camp.QueryID, camp.Status, camp.UserID, camp.ID) + args := []interface{}{camp.QueryID, camp.Status, camp.UserID, camp.ID} + // >>> OPENFRAME(mysql-multitenancy): same fence as DistributedQueryCampaign — an UPDATE by bare + // campaign.ID must not let one tenant overwrite another tenant's campaign row. No-op when unpinned. + // — openframe/docs/mysql-multitenancy-feature.md + if teamID, ok := fleet.OpenframeTeamID(ctx); ok { + sqlStatement += ` AND EXISTS (SELECT 1 FROM queries q WHERE q.id = distributed_query_campaigns.query_id AND q.team_id = ?)` + args = append(args, teamID) + } + // <<< OPENFRAME(mysql-multitenancy) + result, err := ds.writer(ctx).ExecContext(ctx, sqlStatement, args...) if err != nil { return ctxerr.Wrap(ctx, err, "updating distributed query campaign") } @@ -154,6 +163,27 @@ func (ds *Datastore) DistributedQueryCampaignTargetIDs(ctx context.Context, id u } func (ds *Datastore) NewDistributedQueryCampaignTarget(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) { + // >>> OPENFRAME(mysql-multitenancy): a target references its campaign by bare ID — verify the + // campaign belongs to the caller's tenant (via its query's team) before inserting a target row + // against it, matching the fence on DistributedQueryCampaignTargetIDs. No-op when unpinned. + // — openframe/docs/mysql-multitenancy-feature.md + if teamID, ok := fleet.OpenframeTeamID(ctx); ok { + var exists bool + checkStmt := ` + SELECT EXISTS ( + SELECT 1 FROM distributed_query_campaigns dqc + JOIN queries q ON q.id = dqc.query_id + WHERE dqc.id = ? AND q.team_id = ? + ) + ` + if err := sqlx.GetContext(ctx, ds.reader(ctx), &exists, checkStmt, target.DistributedQueryCampaignID, teamID); err != nil { + return nil, ctxerr.Wrap(ctx, err, "checking distributed query campaign tenant") + } + if !exists { + return nil, notFound("DistributedQueryCampaign").WithID(target.DistributedQueryCampaignID) + } + } + // <<< OPENFRAME(mysql-multitenancy) sqlStatement := ` INSERT into distributed_query_campaign_targets ( type, From dc38220c347eae2229cc3ee3e205cc15639ba7f6 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:50 +0000 Subject: [PATCH 14/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/service/endpoint_campaigns.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/server/service/endpoint_campaigns.go b/server/service/endpoint_campaigns.go index ae8aa59fc1b..3c9e6e08bde 100644 --- a/server/service/endpoint_campaigns.go +++ b/server/service/endpoint_campaigns.go @@ -89,19 +89,24 @@ func makeStreamDistributedQueryCampaignResultsHandler(config config.ServerConfig // with the tenant team — leaving the whole campaign stream unfenced and the live_query // activity stamped with a NULL team_id. Re-apply the pin from the upgrade request (read // once: polling transports mutate the session request under a lock). The base ctx stays - // Background so the stream's cancellation semantics are unchanged. Fail closed in shared - // mode: the middleware 401s a headerless upgrade before the handler runs, so an unpinned - // session here means the handler was mounted outside the middleware. + // Background so the stream's cancellation semantics are unchanged. Fail closed + // unconditionally (regardless of shared-mode setting): if session.Request() is nil (some + // sockjs transports, e.g. certain polling/websocket edge cases) or the request carries no + // tenant pin, we cannot prove tenant fencing, so refuse to stream rather than risk + // cross-tenant exposure. // — openframe/docs/mysql-multitenancy-feature.md - if req := session.Request(); req != nil { + req := session.Request() + if req != nil { if teamID, ok := fleet.OpenframeTeamID(req.Context()); ok { ctx = fleet.NewOpenframeTeamContext(ctx, teamID) } } - if _, ok := fleet.OpenframeTeamID(ctx); !ok && fleet.IsOpenframeSharedMode() { - logger.ErrorContext(ctx, "openframe shared mode: rejecting campaign stream without tenant pin") - conn.WriteJSONError("missing tenant") //nolint:errcheck - return + if _, ok := fleet.OpenframeTeamID(ctx); !ok { + if fleet.IsOpenframeSharedMode() || req == nil { + logger.ErrorContext(ctx, "openframe: rejecting campaign stream without tenant pin") + conn.WriteJSONError("missing tenant") //nolint:errcheck + return + } } // <<< OPENFRAME(mysql-multitenancy) From 458541e8491073e2f9a35706a2b1c866bbc3cb1d Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:52 +0000 Subject: [PATCH 15/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/fleet/authz.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/fleet/authz.go b/server/fleet/authz.go index beec02fbe38..984dc6b3800 100644 --- a/server/fleet/authz.go +++ b/server/fleet/authz.go @@ -13,6 +13,7 @@ const ( ActionCreate = "create" // ActionCancelHostActivity refers to canceling an upcoming activity on a host. ActionCancelHostActivity = "cancel_host_activity" + // >>> OPENFRAME(authz-actions): fork-specific authorization actions layered onto upstream authz.go // ActionTransferHost refers to transferring a host between fleets (teams). // This action is permitted for technicians in addition to admin/maintainer/gitops, // so transferring does not require the broader ActionWrite permission. @@ -21,6 +22,7 @@ const ( ActionResend = "resend" // ActionReadSecrets refers to reading secrets/credentials of an entity (e.g. CA private keys, API tokens). ActionReadSecrets = "read_secrets" + // <<< OPENFRAME(authz-actions) // // User specific actions From dec0576618d72316de5c82b4850eccacc0f75f12 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:53 +0000 Subject: [PATCH 16/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/mdm/apple/install_application.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/mdm/apple/install_application.go b/server/mdm/apple/install_application.go index 712d3cf8a70..acc0e09323e 100644 --- a/server/mdm/apple/install_application.go +++ b/server/mdm/apple/install_application.go @@ -1,3 +1,6 @@ +// >>> OPENFRAME(install-application): fork-authored Fleet-variable substitution +// and InstallApplication command building for managed app configuration; not +// present in upstream fleetdm/fleet. See FLEETMDM-001 for sentinel policy. package apple_mdm import ( @@ -282,3 +285,5 @@ func stripPlistWrapper(b []byte) []byte { } return []byte(s) } + +// <<< OPENFRAME(install-application) From a90f4cb932fbbe72f2b8ea4022bf9679745401e4 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:55 +0000 Subject: [PATCH 17/30] fix(FLEETMDM-001): 48 review findings across 30 files --- charts/fleet/templates/deployment.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/charts/fleet/templates/deployment.yaml b/charts/fleet/templates/deployment.yaml index 1b2234d5d31..285bbb0d659 100644 --- a/charts/fleet/templates/deployment.yaml +++ b/charts/fleet/templates/deployment.yaml @@ -222,6 +222,7 @@ spec: # "true" + no tenant UUID ⇒ shared per-request mode (one Fleet per cluster, fail closed). - name: FLEET_OPENFRAME_MULTI_TENANCY_ENABLED value: {{ .Values.fleet.openframe.multiTenancy.enabled | quote }} + {{- if .Values.fleet.openframe.multiTenancy.enabled }} - name: FLEET_OPENFRAME_TENANT_UUID valueFrom: configMapKeyRef: @@ -231,6 +232,7 @@ spec: - name: FLEET_OPENFRAME_TEAM_ID value: {{ .Values.fleet.openframe.multiTenancy.teamId | quote }} {{- end }} + {{- end }} # <<< OPENFRAME(mysql-multitenancy) ## END FLEET SECTION ## BEGIN MYSQL SECTION @@ -683,3 +685,4 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + From 41c9c1844387d06a98ca167b0678ff714d7fe310 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:56 +0000 Subject: [PATCH 18/30] fix(FLEETMDM-001): 48 review findings across 30 files --- orbit/pkg/osquery/flags.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/orbit/pkg/osquery/flags.go b/orbit/pkg/osquery/flags.go index 58e2477e6d7..4761bd5379e 100644 --- a/orbit/pkg/osquery/flags.go +++ b/orbit/pkg/osquery/flags.go @@ -35,11 +35,13 @@ func FleetFlags(osqueryVersion string, fleetURL *url.URL) []string { "--carver_block_size=8000000", } + // >>> OPENFRAME(gzip-flag): enable gzip transport for osquery >= 5.21.0 — openframe/docs/osquery-gzip.md if v, err := semver.NewVersion(osqueryVersion); err == nil { if !semver.New(v.Major(), v.Minor(), v.Patch(), "", "").LessThan(semver.New(5, 21, 0, "", "")) { flags = append(flags, "--tls_accept_gzip=true") } } + // <<< OPENFRAME(gzip-flag) return flags } From 06f9cf3ca6303bc4b92cf649a6edf3a8d0e7efed Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:57 +0000 Subject: [PATCH 19/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/fleet/acme.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/fleet/acme.go b/server/fleet/acme.go index 8ae9b19e08c..eaa6f92aad7 100644 --- a/server/fleet/acme.go +++ b/server/fleet/acme.go @@ -2,6 +2,7 @@ package fleet import "context" +// >>> OPENFRAME(acme-write-service): fork-specific ACME write-service interface // ACMEWriteService is the subset of the ACME service module service // used by the legacy service layer for write operations. type ACMEWriteService interface { @@ -9,3 +10,5 @@ type ACMEWriteService interface { // host_uuid and returns a new path_identifier for the created row. NewACMEEnrollment(ctx context.Context, hostIdentifier string) (string, error) } +// <<< OPENFRAME(acme-write-service) + From 9cd18e3c40a1e84806fafa346acb3932486bfdec Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:38:59 +0000 Subject: [PATCH 20/30] fix(FLEETMDM-001): 48 review findings across 30 files --- .../20260818000001_AddPoliciesOpenframeManagedColumn.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/datastore/mysql/migrations/openframe/20260818000001_AddPoliciesOpenframeManagedColumn.go b/server/datastore/mysql/migrations/openframe/20260818000001_AddPoliciesOpenframeManagedColumn.go index 7da6a18d9a5..29dcbbd324e 100644 --- a/server/datastore/mysql/migrations/openframe/20260818000001_AddPoliciesOpenframeManagedColumn.go +++ b/server/datastore/mysql/migrations/openframe/20260818000001_AddPoliciesOpenframeManagedColumn.go @@ -36,9 +36,11 @@ func Up_20260818000001(tx *sql.Tx) error { return nil } + // >>> OPENFRAME(policies-managed-column): ALTERs upstream `policies` table — openframe/docs/managed-policies.md if _, err := tx.Exec("ALTER TABLE policies ADD COLUMN openframe_managed TINYINT(1) NOT NULL DEFAULT 0"); err != nil { return fmt.Errorf("adding %s.%s column: %w", table, column, err) } + // <<< OPENFRAME(policies-managed-column) return nil } From a25a9c2c4a6f36468c2fa5b38f9568df608aa1ea Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:00 +0000 Subject: [PATCH 21/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/service/labels_util.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/service/labels_util.go b/server/service/labels_util.go index d8040406a32..ab1988a505f 100644 --- a/server/service/labels_util.go +++ b/server/service/labels_util.go @@ -71,9 +71,11 @@ func verifyLabelsToAssociate(ctx context.Context, ds fleet.Datastore, entityTeam uniqueLabelNames = append(uniqueLabelNames, s) } - if entityTeamID == nil { // no-team/all-teams entities can only access global labels + // >>> OPENFRAME(host-assignments): no-team/all-teams entities can only access global labels — openframe/docs/architecture-host-assignments.md + if entityTeamID == nil { entityTeamID = ptr.Uint(0) } + // <<< OPENFRAME(host-assignments) labels, err := loadLabelsFromNames(ctx, ds, uniqueLabelNames, fleet.TeamFilter{User: user, TeamID: entityTeamID}) if err != nil { From 49f2448052ac4888e625ea3cc62de6a26dc9e421 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:02 +0000 Subject: [PATCH 22/30] fix(FLEETMDM-001): 48 review findings across 30 files --- charts/fleet/templates/rbac.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/fleet/templates/rbac.yaml b/charts/fleet/templates/rbac.yaml index ec7edc3950b..f842fb96a65 100644 --- a/charts/fleet/templates/rbac.yaml +++ b/charts/fleet/templates/rbac.yaml @@ -17,6 +17,7 @@ rules: # >>> OPENFRAME(helm): mirror the mysql-tls volume source — openframe/docs/helm-chart.md - {{ default .Values.database.secretName .Values.database.existingSecret }} # <<< OPENFRAME(helm) + # NOTE: the following resourceNames (cache/fleet/osquery secrets, imagePullSecrets) are pre-existing upstream fleetdm/fleet lines, not fork-specific additions. - {{ .Values.cache.secretName }} - {{ .Values.fleet.secretName }} - {{ .Values.osquery.secretName }} @@ -45,3 +46,4 @@ subjects: kind: ServiceAccount name: fleet namespace: {{ .Release.Namespace }} + From 7b7eb79806a03222c82ae7bacf681cc2149245ef Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:03 +0000 Subject: [PATCH 23/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/datastore/mysql/query_results.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/datastore/mysql/query_results.go b/server/datastore/mysql/query_results.go index 3d3fd23847d..39c1df3a877 100644 --- a/server/datastore/mysql/query_results.go +++ b/server/datastore/mysql/query_results.go @@ -72,9 +72,9 @@ func (ds *Datastore) OverwriteQueryResultRows(ctx context.Context, rows []*fleet insertStmt := ` INSERT IGNORE INTO query_results (` + insertCols + `) VALUES ` + strings.Join(valueStrings, ",") - // <<< OPENFRAME(mysql-multitenancy) result, err = tx.ExecContext(ctx, insertStmt, valueArgs...) + // <<< OPENFRAME(mysql-multitenancy) if err != nil { return ctxerr.Wrap(ctx, err, "inserting new rows") } From 41ae2b70577139afbdf892e630a888051fea4c67 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:04 +0000 Subject: [PATCH 24/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/service/scim.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/service/scim.go b/server/service/scim.go index 72b92a78790..3c213bda2cf 100644 --- a/server/service/scim.go +++ b/server/service/scim.go @@ -17,8 +17,10 @@ func getScimDetailsEndpoint(ctx context.Context, _ interface{}, svc fleet.Servic }, nil } +// >>> OPENFRAME(scim-details): file tracked for fork-specific review — openframe/docs/scim.md func (svc *Service) ScimDetails(ctx context.Context) (fleet.ScimDetails, error) { // skipauth: No authorization check needed due to implementation returning only license error. svc.authz.SkipAuthorization(ctx) return fleet.ScimDetails{}, fleet.ErrMissingLicense } +// <<< OPENFRAME(scim-details) From 25164e328345fbf49b41b26b1cb1354aaca9580c Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:06 +0000 Subject: [PATCH 25/30] fix(FLEETMDM-001): 48 review findings across 30 files --- charts/fleet/values.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml index 39e2a0cfdbf..72339ab7634 100644 --- a/charts/fleet/values.yaml +++ b/charts/fleet/values.yaml @@ -129,7 +129,7 @@ fleet: existingSecret: "" # Name of a K8s Secret. If set, secretKeyValue is ignored. secretKeyKey: "FLEET_SETUP_ADMIN_PASSWORD" # Key name within the secret to read the value from. secretKeyValue: "fleet" # Plain text password (for dev/test only). Ignored if secret is set. - # >>> OPENFRAME(mysql-multitenancy): OpenFrame feature block. + # >>> OPENFRAME(mysql-multitenancy): OpenFrame feature block — openframe/docs/mysql-multitenancy.md openframe: multiTenancy: enabled: false @@ -459,3 +459,4 @@ mysql: redis: enabled: false + From 7332da7c56b90ce79670a849034a739bab3caf08 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:07 +0000 Subject: [PATCH 26/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/authz/errors.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/authz/errors.go b/server/authz/errors.go index a4f117466aa..0fc38a1998e 100644 --- a/server/authz/errors.go +++ b/server/authz/errors.go @@ -72,6 +72,7 @@ func (e *Forbidden) LogFields() []interface{} { } } +// >>> OPENFRAME(authz-platform-http-migration): re-export platform_http error types for backward compatibility during the authz->platform_http migration — openframe/docs/authz-platform-http-migration.md // CheckMissing is the error to return when no authorization check was performed // by the service. // @@ -86,3 +87,4 @@ type CheckMissing = platform_http.CheckMissing // Deprecated: Use platform_http.CheckMissingWithResponse instead. This alias is // kept for backward compatibility. var CheckMissingWithResponse = platform_http.CheckMissingWithResponse +// <<< OPENFRAME(authz-platform-http-migration) From 8c6aef7389a5dab47d0a1fe9625c61672ee4e5b9 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:08 +0000 Subject: [PATCH 27/30] fix(FLEETMDM-001): 48 review findings across 30 files --- frontend/interfaces/team.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/interfaces/team.ts b/frontend/interfaces/team.ts index 5d49e8207a4..6db95f214db 100644 --- a/frontend/interfaces/team.ts +++ b/frontend/interfaces/team.ts @@ -135,10 +135,12 @@ export interface IRemoveTeamSecretFormData { export const API_ALL_TEAMS_ID = undefined; export const APP_CONTEXT_ALL_TEAMS_ID = -1; +// >>> OPENFRAME(FLEETMDM-001): rebranded "All teams" summary name to "All fleets" export const APP_CONTEXT_ALL_TEAMS_SUMMARY: ITeamSummary = { id: APP_CONTEXT_ALL_TEAMS_ID, name: "All fleets", } as const; +// <<< OPENFRAME(FLEETMDM-001) export const API_NO_TEAM_ID = 0; export const APP_CONTEXT_NO_TEAM_ID = 0; @@ -155,7 +157,11 @@ export const getTeamDisplayName = (team: ITokenTeam) => ? APP_CONTEXT_NO_TEAM_SUMMARY.name : team.name; +// >>> OPENFRAME(FLEETMDM-001): fork-specific "fleet" rename of getTeamDisplayName +// added for ITokenFleet; reuses APP_CONTEXT_NO_TEAM_SUMMARY.name for both team +// and fleet display names. Preserve this block through upstream syncs. export const getFleetDisplayName = (fleet: ITokenFleet) => fleet.fleet_id === APP_CONTEXT_NO_TEAM_ID ? APP_CONTEXT_NO_TEAM_SUMMARY.name : fleet.name; +// <<< OPENFRAME(FLEETMDM-001) From 3cf775066212a63514408c8ee5deacb753221198 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:10 +0000 Subject: [PATCH 28/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/datastore/mysql/targets.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/targets.go b/server/datastore/mysql/targets.go index 67f42ec19d4..2883c68a633 100644 --- a/server/datastore/mysql/targets.go +++ b/server/datastore/mysql/targets.go @@ -25,7 +25,7 @@ func (ds *Datastore) CountHostsInTargets(ctx context.Context, filter fleet.TeamF // >>> OPENFRAME(mysql-multitenancy): fence target host resolution to this process's pinned team // so a live query cannot target/count another tenant's hosts on a shared DB. The Redis key // prefix already isolates live-query execution/results; this fences the MySQL target set too, - // keeping counts correct and foreign host ids out of campaign targets. + // keeping counts correct and foreign host ids out of campaign targets. — openframe/docs/mysql-multitenancy.md openframeTeamCond := "" var openframeTeamArgs []interface{} if teamID, ok := fleet.OpenframeTeamID(ctx); ok { @@ -148,7 +148,7 @@ func (ds *Datastore) HostIDsInTargets(ctx context.Context, filter fleet.TeamFilt // >>> OPENFRAME(mysql-multitenancy): fence target host resolution to this process's pinned team // so a live query cannot distribute to another tenant's hosts on a shared DB. Injected into the - // WHERE clause (before ORDER BY). + // WHERE clause (before ORDER BY). — openframe/docs/mysql-multitenancy.md openframeTeamCond := "" if teamID, ok := fleet.OpenframeTeamID(ctx); ok { openframeTeamCond = " AND hosts.team_id = ?" From 56ee7099505596053dd88637215b42afc12ff6c7 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:11 +0000 Subject: [PATCH 29/30] fix(FLEETMDM-001): 48 review findings across 30 files --- server/fleet/secrets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/fleet/secrets.go b/server/fleet/secrets.go index 31c4a987e6f..8a26d594ecd 100644 --- a/server/fleet/secrets.go +++ b/server/fleet/secrets.go @@ -7,6 +7,7 @@ import ( const ServerSecretPrefix = "FLEET_SECRET_" +// >>> OPENFRAME(host-secrets): Fleet-internal host-scoped secrets support // HostSecretPrefix is used for host-scoped secrets that are looked up by // enrollment ID rather than by name. These are expanded at command delivery time. // @@ -31,6 +32,8 @@ const ( HostSecretMDMUnlockToken = "MDM_UNLOCK_TOKEN" // nolint:gosec // G101: this is a constant identifier, not a credential ) +// <<< OPENFRAME(host-secrets) + type MissingSecretsError struct { MissingSecrets []string } From 911a46cebb29a1f3e00fa31c6eb79083e19928fe Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:39:13 +0000 Subject: [PATCH 30/30] fix(FLEETMDM-001): 48 review findings across 30 files --- .../20260528211626_AddClearPasscodeRefToHostMDMActions.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/datastore/mysql/migrations/tables/20260528211626_AddClearPasscodeRefToHostMDMActions.go b/server/datastore/mysql/migrations/tables/20260528211626_AddClearPasscodeRefToHostMDMActions.go index 532bc75070a..c652add63fe 100644 --- a/server/datastore/mysql/migrations/tables/20260528211626_AddClearPasscodeRefToHostMDMActions.go +++ b/server/datastore/mysql/migrations/tables/20260528211626_AddClearPasscodeRefToHostMDMActions.go @@ -5,6 +5,7 @@ import ( "fmt" ) +// >>> OPENFRAME(clear-passcode-ref): fork-specific migration to support Android AMAPI clear-passcode commands func init() { MigrationClient.AddMigration(Up_20260528211626, Down_20260528211626) } @@ -31,3 +32,5 @@ ALTER TABLE host_mdm_actions func Down_20260528211626(tx *sql.Tx) error { return nil } + +// <<< OPENFRAME(clear-passcode-ref)