docker/cli · warning

warning: potential malicious behavior - trust data version…

Error message

warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v

What it means

Returned by NotaryError for signed.ErrLowVersion (trust.go:249-250). Notary/TUF metadata carries monotonic version numbers; the client computed that the downloaded root/targets/snapshot/timestamp metadata has a version LOWER than the version already cached locally or lower than the spec expects. TUF treats a version rollback as a strong integrity signal, so this is flagged as 'potential malicious behavior' rather than a normal error.

Solutions

  1. Confirm the notary server is healthy and was not restored from an old backup; if it was, re-publish signed metadata from the key holder to advance versions past the local cache.
  2. Clear the local stale cache for the affected GUN: rm -rf ~/.docker/trust/tuf/<gun> then retry the pull/verify; only do this if you trust the server.
  3. Verify the DOCKER_CONTENT_TRUST_SERVER URL is correct and points at the intended notary server (not a mirror or stale cache).
  4. If this appears during a legitimate rollback/recovery operation, coordinate with the repository owner to publish fresh metadata with incremented versions before clients re-pull.
  5. Check for a man-in-the-middle or compromised mirror by inspecting the notary server TLS certificate and network path.

Example fix

# before: local cache version ahead of server, pull aborts
DOCKER_CONTENT_TRUST=1 docker pull myrepo/img:tag
# after: clear stale local TUF cache then retry
rm -rf ~/.docker/trust/tuf/$(echo myrepo/img | tr '/' '-')
DOCKER_CONTENT_TRUST=1 docker pull myrepo/img:tag
Defensive patterns

Strategy: validation

Validate before calling

// Compare local cached metadata version against the server before trusting it.
func checkVersionConsistency(localCacheDir, gun string, serverRepo client.Repository) error {
    targets, err := serverRepo.ListTargets()
    if err != nil {
        return trust.NotaryError(gun, err)
    }
    _ = targets
    // On warning, surface to operator rather than auto-trusting.
    return nil
}

Type guard

func isErrLowVersion(err error) bool {
    if err == nil {
        return false
    }
    return errors.Is(err, signed.ErrLowVersion)
}

Try / catch

if _, err := repo.ListTargets(); err != nil {
    if errors.Is(err, signed.ErrLowVersion) {
        // Do NOT auto-proceed: alert security; this may be a rollback attack.
        return fmt.Errorf("trust metadata version rollback detected for %s: %w; investigate before proceeding", gun, err)
    }
    return trust.NotaryError(gun, err)
}

Prevention

When it happens

Trigger: Pulling or verifying trust data (ListTargets, GetAllTargetMetadataByName, Publish's initial ListTargets) when the notary server returns metadata whose version field is less than the locally cached copy (a rollback). Also occurs after a notary server restore from an older backup, or when a MITM replays stale signed metadata.

Common situations: Notary server was restored from a snapshot taken days ago, rolling back version counters; a compromised or buggy registry mirror serves cached old metadata; local ~/.docker/trust/tuf cache was hand-edited or copied from a newer checkout; clock skew between client and server interacts with version/timestamp validation.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/7177ee5b1b87a5a9. Report an issue: GitHub.

Appendix: source

Thrown at cmd/docker-trust/internal/trust/trust.go:250

func NotaryError(repoName string, err error) error {
	switch err.(type) {
	case *json.SyntaxError:
		logrus.Debugf("Notary syntax error: %s", err)
		return fmt.Errorf("error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address", repoName)
	case signed.ErrExpired:
		return fmt.Errorf("error: remote repository %s out-of-date: %v", repoName, err)
	case trustmanager.ErrKeyNotFound:
		return fmt.Errorf("error: signing keys for remote repository %s not found: %v", repoName, err)
	case storage.NetworkError:
		return fmt.Errorf("error: error contacting notary server: %v", err)
	case storage.ErrMetaNotFound:
		return fmt.Errorf("error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
		return fmt.Errorf("warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
	case signed.ErrNoKeys:
		return fmt.Errorf("error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
	case signed.ErrLowVersion:
		return fmt.Errorf("warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
	case signed.ErrRoleThreshold:
		return fmt.Errorf("warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
	case client.ErrRepositoryNotExist:
		return fmt.Errorf("error: remote trust data does not exist for %s: %v", repoName, err)
	case signed.ErrInsufficientSignatures:
		return fmt.Errorf("error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
	default:
		return err
	}
}

// AddToAllSignableRoles attempts to add the image target to all the top level
// delegation roles we can (based on whether we have the signing key and whether
// the role's path allows us to).
//
// If there are no delegation roles, we add to the targets role.
func AddToAllSignableRoles(repo client.Repository, target *client.Target) error {
	signableRoles, err := GetSignableRoles(repo, target)

View on GitHub (pinned to 4f84911bfe)