{"record":{"id":"94c90c01044b5044","repo":"netbirdio/netbird","slug":"id-q-is-not-valid","errorCode":null,"errorMessage":"id %q is not valid","messagePattern":"id %q is not valid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/android/profile_manager.go","lineNumber":252,"sourceCode":"\t}\n\n\t// The account file is this package's, not the ServiceManager's, so it must\n\t// go here. The default profile has a fixed filename, so a recreated one\n\t// would otherwise inherit the deleted profile's email as its login_hint.\n\t// Not fatal: the profile itself is gone.\n\tif err := removeProfileEmail(configPath); err != nil {\n\t\tlog.Warnf(\"failed to remove stored account email for profile %s: %v\", id, err)\n\t}\n\n\tlog.Infof(\"removed profile: %s\", id)\n\treturn nil\n}\n\n// getProfileConfigPath returns the config file path for a profile\n// This is needed for Android-specific path handling (netbird.cfg for default profile)\nfunc (pm *ProfileManager) getProfileConfigPath(id string) (string, error) {\n\tif !profilemanager.IsValidProfileFilenameStem(profilemanager.ID(id)) {\n\t\treturn \"\", fmt.Errorf(\"id %q is not valid\", id)\n\t}\n\n\tif id == profilemanager.DefaultProfileName {\n\t\t// Android uses netbird.cfg for default profile instead of default.json\n\t\t// Default profile is stored in root configDir, not in profiles/\n\t\treturn filepath.Join(pm.configDir, defaultConfigFilename), nil\n\t}\n\n\tprofilesDir := filepath.Join(pm.configDir, profilesSubdir)\n\treturn filepath.Join(profilesDir, id+\".json\"), nil\n}\n\n// GetConfigPath returns the config file path for a given profile id\n// Java should call this instead of constructing paths with Preferences.configFile()\nfunc (pm *ProfileManager) GetConfigPath(id string) (string, error) {\n\treturn pm.getProfileConfigPath(id)\n}\n","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/android/profile_manager.go#L234-L270","documentation":"getProfileConfigPath — backing the exported GetConfigPath and used by LogoutProfile, RemoveProfile, and the email lookup — rejects ids that fail IsValidProfileFilenameStem: empty, over 64 characters, containing '/', '\\', '..', or characters outside letters/digits/'_'/'-'. This is the package's path-traversal guard: no filesystem path is built from an unvalidated id. \"default\" is valid and maps to configDir/netbird.cfg instead of profiles/default.json.","triggerScenarios":"GetConfigPath(id) — or LogoutProfile/RemoveProfile internally — with a display name, a filename like \"work.json\" (the dot is not a legal stem character), or any free-form string containing spaces, slashes, or dots.","commonSituations":"Java layer migrating off Preferences.configFile() passing constructed filenames instead of profile ids; ids from older app versions with unsanitized names.","solutions":["Pass ids exactly as returned by ListProfiles or GetActiveProfile","Use \"default\" for the default profile","Validate free-form input against the stem rules (letters, digits, '_', '-', max 64) before calling"],"exampleFix":"// before\npath, err := pm.GetConfigPath(\"work.json\") // dots are invalid in a stem\n\n// after\npath, err := pm.GetConfigPath(\"default\")   // or an id obtained from ListProfiles","handlingStrategy":"validation","validationCode":"// Gate every path API on a stem-valid id\nif !isValidProfileID(id) {\n\treturn errors.New(\"reject before calling GetConfigPath\")\n}","typeGuard":"func isValidProfileID(id string) bool {\n\tif id == \"\" || len(id) > 64 {\n\t\treturn false\n\t}\n\tif strings.ContainsAny(id, `/\") || strings.Contains(id, \"..\") {\n\t\treturn false\n\t}\n\tif filepath.Base(id) != id {\n\t\treturn false\n\t}\n\tfor _, r := range id {\n\t\tif !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-') {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"path, err := pm.GetConfigPath(id)\nif err != nil && strings.Contains(err.Error(), \"is not valid\") {\n\t// caller bug: a display name/filename was passed instead of a profile id\n}","preventionTips":["Pass ids verbatim from ListProfiles/GetActiveProfile; never construct them","Use \"default\" for the default profile — it maps to netbird.cfg","Remember '.' is invalid in a stem: \"work.json\" must be passed as \"work\""],"tags":["android","go","profile","validation","path-traversal"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}