labstack/echo · warning
failed to unescape path variable: %w
Error message
failed to unescape path variable: %w
What it means
Returned by StaticDirectoryHandler (echo.go:657-661) when url.PathUnescape of the wildcard path param fails. The client sent a %-sequence that is not valid percent-encoding (e.g. %zz or a truncated %2). The handler only runs this unescape when disablePathUnescaping is false.
Source
Thrown at echo.go:660
// StaticDirectoryHandler creates handler function to serve files from provided file system
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
//
// Note: when disablePathUnescaping=false, the handler decodes the wildcard param before serving.
// If route guards (e.g. e.GET("/admin/*", forbidden)) are used to restrict parts of the
// filesystem, an encoded separator (%2F) or encoded dot-dot (%2E%2E) in the URL can resolve to
// a path that the router never matched against the guard route. Enabling
// RouterConfig.UseEscapedPathForMatching does NOT fix this — it changes which path the router
// uses for matching but still lets path.Clean resolve ".." segments into a guarded directory.
// Do not rely on route guards alone to restrict a filesystem served by this handler.
// See https://github.com/labstack/echo/security/advisories/GHSA-vfp3-v2gw-7wfq
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
return func(c *Context) error {
p := c.Param("*")
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
tmpPath, err := url.PathUnescape(p)
if err != nil {
return fmt.Errorf("failed to unescape path variable: %w", err)
}
p = tmpPath
}
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/`
// as invalid
// Use path.Clean (not filepath.Clean): fs.FS paths are always forward-slash, so a backslash must stay a literal
// character rather than being interpreted as a separator on Windows (which would resolve a file across a boundary
// the router never matched on, the same Windows backslash traversal class as GHSA-pgvm-wxw2-hrv9).
name := path.Clean(strings.TrimPrefix(p, "/"))
fi, err := fs.Stat(fileSystem, name)
if err != nil {
return ErrNotFound
}
// If the request is for a directory and does not end with "/" redirect to path which ends with "/"
p = c.Request().URL.Path
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {View on GitHub (pinned to 05489dc173)
Solutions
- Let the default HTTPErrorHandler turn this into a 404/400
- Override e.HTTPErrorHandler to map the error to a clean 400 and avoid echoing raw path bytes
- If serving untrusted paths, review whether disablePathUnescaping or router-level unescaping fits your threat model
Example fix
// before: rely on the default handler, which leaks detail
// after: custom error handler masks the cause
e.HTTPErrorHandler = func(err error, c echo.Context) {
if strings.Contains(err.Error(), "unescape path variable") {
_ = c.String(http.StatusBadRequest, "bad path encoding")
return
}
c.DefaultHTTPErrorHandler(err, c)
} Defensive patterns
Strategy: try-catch
Validate before calling
// nothing to validate pre-handler; reject malformed escapes at the edge (reverse proxy / WAF) when possible
Try / catch
e.HTTPErrorHandler = func(err error, c echo.Context) {
if errors.Is(err, echo.ErrNotFound) || strings.Contains(err.Error(), "unescape path variable") {
_ = c.String(http.StatusBadRequest, "bad request")
return
}
c.DefaultHTTPErrorHandler(err, c)
} Prevention
- Sanitise URLs at the reverse proxy
- Do not log or echo raw untrusted path bytes
- Use a custom HTTPErrorHandler to hide detail
When it happens
Trigger: A request to a static route whose wildcard segment contains malformed percent-encoding, while disablePathUnescaping is false (the default behavior).
Common situations: Bots/scanners sending crafted URLs; clients with buggy URL encoders; double-encoding mishaps that produce invalid sequences.
Related errors
- file does not implement io.ReadSeeker
- at least one AllowOrigins is required or UnsafeAllowOriginFu
- * as allowed origin and AllowCredentials=true is insecure an
- echo key-auth middleware requires a validator function
AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04).
Data as JSON: /data/errors/b8eb09638475f27e.json.
Report an issue: GitHub.