JuliusBrussee/caveman · error

backslash is not allowed in path

Error message

backslash is not allowed in path

What it means

validatePathComponents rejects any path containing a backslash byte. Backslashes are path separators on Windows and are normalized differently by various decoders, so allowing them could change route identity across proxy hops. Applies to /compat/ paths and to configured upstream base URLs.

Solutions

  1. Replace the backslash with a forward slash in the path
  2. Fix the upstream BaseURL config value to use forward slashes
  3. If the backslash is meaningful data, move it to the query string or request body, properly percent-encoded

Example fix

// before
BaseURL: "https://upstream.example.com\v1"

// after
BaseURL: "https://upstream.example.com/v1"
Defensive patterns

Strategy: validation

Validate before calling

if strings.Contains(rawURL, "\\") {
    return errors.New("backslash not allowed in URL")
}

Try / catch

if err := ValidateBaseURL(cfg.BaseURL); err != nil {
    return fmt.Errorf("invalid upstream config: %w", err)
}

Prevention

When it happens

Trigger: Request path like /compat/openai\v1/chat; an upstream BaseURL configured with a backslash; a client copying a Windows-style path into an API URL.

Common situations: Windows developers pasting file-system-like paths into API clients; config values copied from Windows machines; malicious clients probing separator-confusion.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/ed4d30896257f477. Report an issue: GitHub.

Appendix: source

Thrown at proxy/providers/openaicompat/openaicompat.go:489

	if !strings.HasPrefix(u.Path, "/compat/") {
		return nil
	}
	return validateCompatPath(u.Path, u.RawPath)
}

func validateCompatPath(path, rawPath string) error {
	if !strings.HasPrefix(path, "/compat/") {
		return nil
	}
	if err := validatePathComponents(path, rawPath); err != nil {
		return fmt.Errorf("compat route path rejected: %w", err)
	}
	return nil
}

func validatePathComponents(path, rawPath string) error {
	if strings.Contains(path, `\`) {
		return fmt.Errorf("backslash is not allowed in path")
	}
	segments := strings.Split(path, "/")
	for i, segment := range segments {
		if segment == "" && i > 0 && i < len(segments)-1 {
			return fmt.Errorf("repeated path separators are not allowed")
		}
		if segment == "." || segment == ".." {
			return fmt.Errorf("dot segments are not allowed in path")
		}
	}
	// URL.Path is decoded by net/url while RawPath retains a valid escaped
	// spelling. Reject separators, backslashes, and dot bytes in either form so
	// a path cannot change route identity after another decoder or proxy hop.
	for _, escape := range []string{"%2f", "%5c", "%2e"} {
		if strings.Contains(strings.ToLower(path), escape) || strings.Contains(strings.ToLower(rawPath), escape) {
			return fmt.Errorf("ambiguous escaped path sequence %s", escape)
		}
	}

View on GitHub (pinned to 3ee70a1026)