{"record":{"id":"1598505cde222e54","repo":"amir20/dozzle","slug":"invalid-username-contains-path-separator-or-trave","errorCode":null,"errorMessage":"invalid username: contains path separator or traversal","messagePattern":"invalid username: contains path separator or traversal","errorType":"validation","errorClass":"errInvalidUsername","httpStatus":null,"severity":"error","filePath":"internal/profile/disk.go","lineNumber":53,"sourceCode":"\tHourStyle         string  `json:\"hourStyle,omitempty\"`\n\tDateLocale        string  `json:\"dateLocale,omitempty\"`\n\tLocale            string  `json:\"locale\"`\n\tGroupContainers   string  `json:\"groupContainers,omitempty\"`\n}\n\ntype Profile struct {\n\tSettings              *Settings `json:\"settings,omitempty\"`\n\tPinned                []string  `json:\"pinned\"`\n\tVisibleKeys           []any     `json:\"visibleKeys,omitempty\"`\n\tReleaseSeen           string    `json:\"releaseSeen,omitempty\"`\n\tCollapsedGroups       []string  `json:\"collapsedGroups\"`\n\tDismissedImageUpdates []string  `json:\"dismissedImageUpdates,omitempty\"`\n\tDismissedLinkHint     bool      `json:\"dismissedLinkHint,omitempty\"`\n}\n\nvar dataPath string\nvar mux = &sync.Mutex{}\nvar errInvalidUsername = errors.New(\"invalid username: contains path separator or traversal\")\n\nfunc init() {\n\tpath, err := filepath.Abs(\"./data\")\n\tif err != nil {\n\t\tlog.Fatal().Err(err).Msg(\"Unable to get absolute path\")\n\t\treturn\n\t}\n\tif _, err := os.Stat(path); os.IsNotExist(err) {\n\t\tif err := os.Mkdir(path, 0755); err != nil {\n\t\t\tlog.Fatal().Err(err).Msg(\"Unable to create data directory\")\n\t\t\treturn\n\t\t}\n\t}\n\tdataPath = path\n}\n\nfunc safePath(username string) (string, error) {\n\tclean := filepath.Base(username)","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/profile/disk.go#L35-L71","documentation":"errInvalidUsername is returned by safePath in the profile package when the supplied username is not a safe single path element. It guards against path traversal and separators that would escape the per-user data directory (e.g. \"../host\" or \"a/b\"). filepath.Base(username) is compared to the original, and \".\" and \"..\" are explicitly rejected.","triggerScenarios":"Calling profile.Load(username), profile.UpdateFromReader(username, ...), or save() with a username containing '/', '\\\\', or equal to \".\" or \"..\" (any value where filepath.Base(username) != username).","commonSituations":"Auth backends that derive usernames from headers or emails with slashes; misconfigured forward-proxy auth passing the full user DN; calling Load(\"\") (Base(\"\")==\".\"); tests feeding raw user-supplied names.","solutions":["Validate the username before calling profile functions: reject strings containing path separators, '.', '..', or empty strings","Normalize/derive an internal identifier (e.g. hash or sanitized ID) from the upstream username instead of using it verbatim","If the username legitimately contains separators, encode it (URL-safe base64) before use","Check err with errors.Is(err, profile.errInvalidUsername) to return a 400 to the client instead of a 500"],"exampleFix":"// before\nprofile.UpdateFromReader(r.Header.Get(\"X-Forwarded-User\"), body)\n// after\nuser := r.Header.Get(\"X-Forwarded-User\")\nif user == \"\" || strings.ContainsAny(user, \"/\\\\\") || user == \".\" || user == \"..\" {\n    http.Error(w, \"invalid username\", http.StatusBadRequest)\n    return\n}\nprofile.UpdateFromReader(user, body)","handlingStrategy":"validation","validationCode":"func validUsername(u string) bool {\n    return u != \"\" && u != \".\" && u != \"..\" && !strings.ContainsAny(u, \"/\\\\\") && u == filepath.Base(u)\n}\nif !validUsername(username) { return errInvalidUsername }","typeGuard":"func isSafeUsername(u string) bool { return filepath.Base(u) == u && u != \".\" && u != \"..\" }","tryCatchPattern":"if err := profile.UpdateFromReader(user, r.Body); err != nil {\n    if errors.Is(err, profile.ErrInvalidUsername) { http.Error(w, \"invalid username\", 400); return }\n    http.Error(w, \"internal error\", 500)\n}","preventionTips":["Sanitize or hash usernames from auth headers before using them as file keys","Add a unit test covering usernames with '/', '..', '.', and empty strings","Never pass raw user input to filesystem-path-building functions"],"tags":["go","path-traversal","security","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}