JuliusBrussee/caveman · error

ambiguous escaped path sequence

Error message

ambiguous escaped path sequence %s

What it means

URL.Path is decoded by net/url while RawPath preserves the escaped spelling. validatePathComponents rejects %2f (/), %5c (\), and %2e (.) in either spelling, case-insensitively, so an ambiguous path cannot flip meaning after another decoder or proxy hop processes it.

Solutions

  1. Move the value containing slashes/escapes out of the path (query parameter or JSON body)
  2. Send the value exactly once-encoded so no %2f/%5c/%2e remains in the path
  3. Percent-decode and re-encode the id correctly in the client; use only path-safe characters in path segments
  4. Check for double encoding in intermediate clients/proxies and remove it

Example fix

// before
GET /compat/openai/v1/models/org%2fmodel-1

// after
GET /compat/openai/v1/models/org-model-1  (or pass id via request body)
Defensive patterns

Strategy: validation

Validate before calling

for _, esc := range []string{"%2f", "%5c", "%2e"} {
    if strings.Contains(strings.ToLower(u.RawPath), esc) {
        return fmt.Errorf("ambiguous escape %s", esc)
    }
}

Try / catch

if err := ValidateRequestPath(req.URL); err != nil {
    http.Error(w, "ambiguous path encoding", http.StatusBadRequest)
    return
}

Prevention

When it happens

Trigger: Path containing %2f, %5c, or %2e (in any case) in either Path or RawPath, e.g. /compat/openai/v1/models%2fcompletions; double-encoded sequences like %252f surviving one decode; data-bearing path segments where ids contain encoded slashes.

Common situations: Model names or resource ids containing '/' being stuffed into the URL path instead of the body; clients double-encoding values; WAF-evasion probes.

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/7aac0ae3c29a1995. Report an issue: GitHub.

Appendix: source

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

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)
		}
	}
	return nil
}

func parseBaseURL(raw, provider string) (*url.URL, error) {
	if strings.TrimSpace(raw) == "" {
		return nil, fmt.Errorf("provider %q has no configured upstream URL", provider)
	}
	u, err := url.Parse(strings.TrimSpace(raw))
	if err != nil {
		return nil, err
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return nil, fmt.Errorf("provider %q upstream URL scheme %q is not allowed", provider, u.Scheme)
	}
	if !u.IsAbs() || u.Host == "" || u.Hostname() == "" {
		return nil, fmt.Errorf("provider %q upstream URL must be an absolute URL with a host", provider)

View on GitHub (pinned to 3ee70a1026)