amir20/dozzle · error · errInvalidUsername

invalid username: contains path separator or traversal

Error message

invalid username: contains path separator or traversal

What it means

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.

Solutions

  1. Validate the username before calling profile functions: reject strings containing path separators, '.', '..', or empty strings
  2. Normalize/derive an internal identifier (e.g. hash or sanitized ID) from the upstream username instead of using it verbatim
  3. If the username legitimately contains separators, encode it (URL-safe base64) before use
  4. Check err with errors.Is(err, profile.errInvalidUsername) to return a 400 to the client instead of a 500

Example fix

// before
profile.UpdateFromReader(r.Header.Get("X-Forwarded-User"), body)
// after
user := r.Header.Get("X-Forwarded-User")
if user == "" || strings.ContainsAny(user, "/\\") || user == "." || user == ".." {
    http.Error(w, "invalid username", http.StatusBadRequest)
    return
}
profile.UpdateFromReader(user, body)
Defensive patterns

Strategy: validation

Validate before calling

func validUsername(u string) bool {
    return u != "" && u != "." && u != ".." && !strings.ContainsAny(u, "/\\") && u == filepath.Base(u)
}
if !validUsername(username) { return errInvalidUsername }

Type guard

func isSafeUsername(u string) bool { return filepath.Base(u) == u && u != "." && u != ".." }

Try / catch

if err := profile.UpdateFromReader(user, r.Body); err != nil {
    if errors.Is(err, profile.ErrInvalidUsername) { http.Error(w, "invalid username", 400); return }
    http.Error(w, "internal error", 500)
}

Prevention

When it happens

Trigger: Calling profile.Load(username), profile.UpdateFromReader(username, ...), or save() with a username containing '/', '\\', or equal to "." or ".." (any value where filepath.Base(username) != username).

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/1598505cde222e54. Report an issue: GitHub.

Appendix: source

Thrown at internal/profile/disk.go:53

	HourStyle         string  `json:"hourStyle,omitempty"`
	DateLocale        string  `json:"dateLocale,omitempty"`
	Locale            string  `json:"locale"`
	GroupContainers   string  `json:"groupContainers,omitempty"`
}

type Profile struct {
	Settings              *Settings `json:"settings,omitempty"`
	Pinned                []string  `json:"pinned"`
	VisibleKeys           []any     `json:"visibleKeys,omitempty"`
	ReleaseSeen           string    `json:"releaseSeen,omitempty"`
	CollapsedGroups       []string  `json:"collapsedGroups"`
	DismissedImageUpdates []string  `json:"dismissedImageUpdates,omitempty"`
	DismissedLinkHint     bool      `json:"dismissedLinkHint,omitempty"`
}

var dataPath string
var mux = &sync.Mutex{}
var errInvalidUsername = errors.New("invalid username: contains path separator or traversal")

func init() {
	path, err := filepath.Abs("./data")
	if err != nil {
		log.Fatal().Err(err).Msg("Unable to get absolute path")
		return
	}
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.Mkdir(path, 0755); err != nil {
			log.Fatal().Err(err).Msg("Unable to create data directory")
			return
		}
	}
	dataPath = path
}

func safePath(username string) (string, error) {
	clean := filepath.Base(username)

View on GitHub (pinned to d9463cbe21)