netbirdio/netbird · error

id '%s' is not valid

Error message

id '%s' is not valid

What it means

LogoutProfile re-validates the profile id with IsValidProfileFilenameStem and rejects ids that are empty, longer than 64 characters, contain '/', '\', '..', or any character outside letters/digits/underscore/hyphen — a path-traversal guard before the config path is used. Note that in the current code this exact check is shadowed: getProfileConfigPath runs the same validation first (line 175), so an invalid id normally surfaces earlier as 'id %q is not valid'.

Source

Thrown at client/android/profile_manager.go:181

	// Use ServiceManager (creates profile in profiles/ directory)
	profile, err := pm.serviceMgr.AddProfile(profileName, androidUsername)
	if err != nil {
		return fmt.Errorf("failed to add profile: %w", err)
	}

	log.Infof("created new profile: %s", profile.ID)
	return nil
}

// LogoutProfile logs out from a profile (clears authentication)
func (pm *ProfileManager) LogoutProfile(id string) error {
	configPath, err := pm.getProfileConfigPath(id)
	if err != nil {
		return err
	}

	if !profilemanager.IsValidProfileFilenameStem(profilemanager.ID(id)) {
		return fmt.Errorf("id '%s' is not valid", id)
	}

	// Check if profile exists
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		return fmt.Errorf("profile '%s' does not exist", id)
	}

	// Read current config using internal profilemanager
	config, err := profilemanager.ReadConfig(configPath)
	if err != nil {
		return fmt.Errorf("failed to read profile config: %w", err)
	}

	// Clear authentication by removing private key and SSH key
	config.PrivateKey = ""
	config.SSHKey = ""

	// Save config using internal profilemanager

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Pass only ids obtained from ListProfiles or GetActiveProfile
  2. Use "default" for the default profile
  3. Reject path-like free-form input in the UI layer before calling

Example fix

// before
pm.LogoutProfile(profile.Name) // display name; may contain dots/spaces → rejected

// after
pm.LogoutProfile(profile.ID) // stem-safe id exactly as returned by ListProfiles
Defensive patterns

Strategy: validation

Validate before calling

// Reject non-stem ids before calling LogoutProfile (mirrors IsValidProfileFilenameStem)
if !isValidProfileID(id) {
	// do not call; ids must come from ListProfiles/GetActiveProfile
}

Type guard

func isValidProfileID(id string) bool {
	if id == "" || len(id) > 64 {
		return false
	}
	if strings.ContainsAny(id, `/") || strings.Contains(id, "..") {
		return false
	}
	if filepath.Base(id) != id {
		return false
	}
	for _, r := range id {
		if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-') {
			return false
		}
	}
	return true
}

Try / catch

if err := pm.LogoutProfile(id); err != nil {
	if strings.Contains(err.Error(), "is not valid") {
		// caller bug: a display name or path was passed instead of a profile ID
	} else if strings.Contains(err.Error(), "does not exist") {
		// stale entry; refresh the list
	}
}

Prevention

When it happens

Trigger: LogoutProfile called with a display name, a filename like "work.json" (the dot is not a legal stem character), or any path-like/free-form string instead of the raw ID returned by ListProfiles.

Common situations: Java/Kotlin UI passing the profile Name field instead of ID, hardcoded ids containing dots or spaces, ids from an older app version that predated sanitization.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/8fc70a75d18d2e10. Report an issue: GitHub.