VYPR
Moderate severityNVD Advisory· Published Feb 26, 2026· Updated Feb 26, 2026

Fleet: Unauthenticated Android device disenrollment vulnerability via Pub/Sub endpoint

CVE-2026-24004

Description

Fleet is open source device management software. In versions prior to 4.80.1, a vulnerability in Fleet’s Android MDM Pub/Sub handling could allow unauthenticated requests to trigger device unenrollment events. This may result in unauthorized removal of individual Android devices from Fleet management. If Android MDM is enabled, an attacker could send a crafted request to the Android Pub/Sub endpoint to unenroll a targeted Android device from Fleet without authentication. This issue does not grant access to Fleet, allow execution of commands, or provide visibility into device data. Impact is limited to disruption of Android device management for the affected device. Version 4.80.1 fixes the issue. If an immediate upgrade is not possible, affected Fleet users should temporarily disable Android MDM.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/fleetdm/fleet/v4Go
< 4.80.14.80.1

Affected products

1

Patches

1
24dd2257ae71

Rework Android pubsub endpoint to avoid pubsub retry loops on Android MDM disablement (#38403)

https://github.com/fleetdm/fleetJordan MontgomeryJan 15, 2026via ghsa
3 files changed · +16 14
  • server/mdm/android/service/pubsub.go+9 10 modified
    @@ -4,6 +4,7 @@ import (
     	"context"
     	"encoding/base64"
     	"fmt"
    +	"net/http"
     	"strings"
     	"time"
     
    @@ -69,7 +70,9 @@ func (svc *Service) ProcessPubSubPush(ctx context.Context, token string, message
     
     func (svc *Service) authenticatePubSub(ctx context.Context, token string) error {
     	svc.authz.SkipAuthorization(ctx)
    -	_, err := svc.checkIfAndroidNotConfigured(ctx)
    +	// On a simple not configured error return status OK to avoid PubSub retry looping after
    +	// disabling Android MDM
    +	_, err := svc.checkIfAndroidNotConfigured(ctx, http.StatusOK)
     	if err != nil {
     		return err
     	}
    @@ -107,12 +110,13 @@ func (svc *Service) getClientAuthenticationSecret(ctx context.Context) (string,
     }
     
     func (svc *Service) handlePubSubStatusReport(ctx context.Context, token string, rawData []byte) error {
    -	// We allow DELETED notification type to be received since user may be in the process of disabling Android MDM.
    -	// Otherwise, we authenticate below in authenticatePubSub
    -	svc.authz.SkipAuthorization(ctx)
    +	err := svc.authenticatePubSub(ctx, token)
    +	if err != nil {
    +		return err
    +	}
     
     	var device androidmanagement.Device
    -	err := json.Unmarshal(rawData, &device)
    +	err = json.Unmarshal(rawData, &device)
     	if err != nil {
     		return ctxerr.Wrap(ctx, err, "unmarshal Android status report message")
     	}
    @@ -187,11 +191,6 @@ func (svc *Service) handlePubSubStatusReport(ctx context.Context, token string,
     		return nil
     	}
     
    -	err = svc.authenticatePubSub(ctx, token)
    -	if err != nil {
    -		return err
    -	}
    -
     	host, err := svc.getExistingHost(ctx, &device)
     	if err != nil {
     		return ctxerr.Wrap(ctx, err, "getting existing Android host")
    
  • server/mdm/android/service/pubsub_test.go+4 1 modified
    @@ -5,6 +5,7 @@ import (
     	"encoding/base64"
     	"encoding/json"
     	"errors"
    +	"net/http"
     	"os"
     	"strconv"
     	"strings"
    @@ -84,8 +85,10 @@ func TestPubSubEnrollment(t *testing.T) {
     				EnrollmentTokenData: string(enrollTokenData),
     			})
     			err = svc.ProcessPubSubPush(context.Background(), "invalid", enrollmentMessage)
    -			require.Error(t, err)
     			require.Equal(t, "validation failed: android Android MDM is NOT configured", err.Error())
    +			sc, ok := err.(interface{ Status() int })
    +			require.True(t, ok, "error should implement Status() interface")
    +			require.Equal(t, http.StatusOK, sc.Status())
     		})
     
     		t.Run("if android token is invalid", func(t *testing.T) {
    
  • server/mdm/android/service/service.go+3 3 modified
    @@ -532,7 +532,7 @@ func (svc *Service) CreateEnrollmentToken(ctx context.Context, enrollSecret, idp
     	// We call SkipAuthorization here to avoid explicitly calling it when errors occur.
     	svc.authz.SkipAuthorization(ctx)
     
    -	_, err := svc.checkIfAndroidNotConfigured(ctx)
    +	_, err := svc.checkIfAndroidNotConfigured(ctx, http.StatusConflict)
     	if err != nil {
     		return nil, err
     	}
    @@ -606,15 +606,15 @@ func (svc *Service) CreateEnrollmentToken(ctx context.Context, enrollSecret, idp
     	}, nil
     }
     
    -func (svc *Service) checkIfAndroidNotConfigured(ctx context.Context) (*fleet.AppConfig, error) {
    +func (svc *Service) checkIfAndroidNotConfigured(ctx context.Context, statusOfError int) (*fleet.AppConfig, error) {
     	// This call uses cached_mysql implementation, so it's safe to call it multiple times
     	appConfig, err := svc.ds.AppConfig(ctx)
     	if err != nil {
     		return nil, ctxerr.Wrap(ctx, err, "getting app config")
     	}
     	if !appConfig.MDM.AndroidEnabledAndConfigured {
     		return nil, fleet.NewInvalidArgumentError("android",
    -			"Android MDM is NOT configured").WithStatus(http.StatusConflict)
    +			"Android MDM is NOT configured").WithStatus(statusOfError)
     	}
     	return appConfig, nil
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.