Neutron, our AI engine, scored 97.1% on UC Berkeley's CyberGym benchmark. Learn more

Deep Dive

Deep Dive

Echo WebSocket Proxy Auth Bypass (GHSA-cxf3-9h37-7rr8)

Rejected WebSocket upgrades through Echo's Proxy middleware leave a raw tunnel open to the upstream, and later HTTP requests on that connection skip Echo's router and route middleware, so authentication enforced only at Echo routes is bypassed. The GitHub Advisory Database rates it Critical, CVSS v4.0 9.1, while Echo's own advisory rates it Moderate. Fixed in v4.16.1 and v5.4.1, released October 22, 2026. No public proof of concept and no exploitation in the wild are known.

TL;DR

  • When a client asked for a WebSocket upgrade on a route handled by Echo's Proxy middleware, older versions hijacked the client connection and dialed the upstream directly, copying bytes both ways without ever reading the upstream's response. If the upstream rejected the upgrade with an ordinary HTTP response and kept the connection alive, the tunnel stayed open.
  • Any further HTTP request the attacker sent on that same connection was forwarded straight to the upstream, so Echo's router and route middleware, where most applications enforce authentication and authorization, never saw it.
  • The GitHub Advisory Database review rates the issue Critical, CVSS v4.0 9.1; Echo's maintainers rate their own advisory Moderate. CWE-444 and CWE-863 are recorded. It is not in the KEV catalog, no public exploit exists, and no exploitation in the wild is known as of October 24, 2026.
  • Upgrade to v4.16.1 or later on the v4 line, or v5.4.1 or later on v5. Until then, reject WebSocket upgrade requests before they reach proxy routes, or enforce authorization at the upstream independently.

Am I affected?

You are affected if all of these hold:

  • Your application or API gateway uses Echo, module github.com/labstack/echo/v4 at 4.16.0 or older, or github.com/labstack/echo/v5 at 5.4.0 or older.
  • A route or group uses Echo's built-in Proxy middleware (middleware.Proxy, ProxyWithConfig, or the balancer helpers), so Echo forwards requests to an upstream.
  • A client can reach that proxied route and request a WebSocket upgrade on it.
  • Security checks for the upstream's resources live at Echo's routes or middleware rather than at the upstream itself.

You are not affected if you never mount the Proxy middleware: plain Echo routing, authentication middleware, static file serving and the rest are outside this bug. The bypass also does not let an attacker reach any host other than the proxy's configured upstream, and it does not defeat the upstream's own authentication. pkg.go.dev lists 32,160 packages importing github.com/labstack/echo/v4 (checked October 24, 2026), and only the subset that mounts the Proxy middleware is exposed.

Branch Affected versions Fixed version
echo/v4 >= 4.0.0, < 4.16.1 4.16.1
echo/v5 >= 5.0.0, < 5.4.1 5.4.1

The advisory notes runtime reproductions confirming v4.15.0, v4.16.0, v5.0.0 and v5.4.0, and that source history shows the missing validation as far back as v4.0.0.

Check your modules and their versions:

go list -m github.com/labstack/echo/v4
go list -m all | grep labstack/echo
govulncheck ./...
# In a Docker image that has the module cache
docker run --rm <image> sh -c "go list -m github.com/labstack/echo/v4 2>/dev/null || grep labstack/echo /go.mod"

# Directly in the build files
grep -rn "labstack/echo" go.mod go.sum

govulncheck flags the affected module range against this advisory when it is in your build graph.

How do I fix it?

Upgrade the module:

go get github.com/labstack/echo/v4@v4.16.1
# or, on the v5 line
go get github.com/labstack/echo/v5@v5.4.1
go mod tidy

Note the release requirement on v4.16.1: patched golang.org/x/ dependencies need Go 1.26 or later, so your build toolchain may need an update too. Later patch releases, such as v4.16.2 published on October 23, 2026, carry the same fix plus follow-ups, so pinning to the newest patch of your line also works.

If you cannot upgrade immediately, the advisory gives two workarounds, and one important non-workaround:

  • Reject WebSocket upgrade requests before they reach the affected proxy routes. A small middleware in front of the proxy that answers upgrade attempts with a plain error closes the entry point.
  • Enforce authorization independently at every upstream resource, so requests that bypass Echo's route layer still meet real checks.
  • Merely returning a non-101 response from the upstream does not help, because that rejection is exactly what leaves the vulnerable tunnel open.

The limits are direct: the first workaround disables WebSocket proxying entirely, and the second protects only resources that have their own checks, which is the application's work to verify resource by resource.

What is the root cause?

Echo's Proxy middleware chose a raw byte tunnel for WebSocket requests before checking whether the upstream accepted the upgrade, so the tunnel kept running after the upstream said no.

The dispatch, in middleware/proxy.go at v4.16.0, line 391:

// Proxy
switch {
case c.IsWebSocket():
    proxyRaw(tgt, c, config).ServeHTTP(res, req)
default: // even SSE requests
    proxyHTTP(tgt, c, config).ServeHTTP(res, req)
}

proxyRaw (line 134) is the problem. It hijacks the client connection, dials the upstream, and copies bytes in both directions:

in, _, err := c.Response().Hijack()
...
out, err := dialFunc(c.Request().Context(), "tcp", t.URL.Host)
...
// Write header
err = r.Write(out)
...
errCh := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
    _, copyErr := io.Copy(dst, src)
    errCh <- copyErr
}

go cp(out, in)
go cp(in, out)

Notice what is missing: r.Write(out) sends the upgrade request, and then two io.Copy loops shuttle bytes forever, but nothing ever reads the upstream's HTTP response to the upgrade request, and nothing conditions the tunnel on the upstream answering 101 Switching Protocols. Compare with proxyHTTP, the non-WebSocket branch, which delegates to Go's httputil.NewSingleHostReverseProxy, a component that does validate upstream responses.

The consequences follow directly. Once the client connection is hijacked, Echo's request handling for that connection is over. If the upstream replies to the upgrade attempt with an ordinary HTTP response and, critically, keeps the TCP connection alive with HTTP keep-alive, the raw copy loop keeps forwarding. The client receives the upstream's rejection bytes, and can then send another ordinary HTTP request on the same connection. cp(out, in) writes it verbatim to the upstream, and the upstream's reply flows back the same way. Echo's router, route middleware, rate limits and route-level auth are all bypassed for those requests, because from Echo's point of view the connection left the HTTP world at hijack time.

How is it exploited?

No public proof of concept exists. The advisory, published by maintainer vishr with credit to reporter @Duodum, describes the mechanics and says the maintainers reproduced the flow at runtime on v4.15.0, v4.16.0, v5.0.0 and v5.4.0. The attack path it describes:

  1. The attacker opens a connection to an Echo route that uses the Proxy middleware and sends an HTTP request with WebSocket upgrade headers.
  2. Echo answers the client side by hijacking the connection, and asks the configured upstream for the upgrade.
  3. The upstream is not listening for WebSocket on that path, or refuses it, and answers with an ordinary HTTP response, for example a 200 or a 404, on a connection it keeps alive.
  4. The raw copy loop keeps running, so the attacker now holds a persistent pipe to the upstream that Echo no longer inspects.
  5. The attacker writes ordinary HTTP requests into that pipe. Each one is forwarded byte for byte to the upstream, and each response comes straight back.
  6. Any path or resource the upstream serves without its own authorization check is now reachable, even when Echo's routes would have required authentication for the equivalent request.

Every condition in that chain matters: an accessible proxy route, an upstream that rejects without closing, and upstream resources that trust the proxy's front-door checks.

Attack flow

flowchart LR A["Attacker sends WebSocket
upgrade to proxy route"] --> B["Echo picks proxyRaw and
hijacks the client connection"] B --> C["Raw request written to upstream,
upstream response never read"] C --> D["Upstream rejects with plain HTTP
and keeps connection alive"] D --> E["Tunnel keeps copying,
upgrade check missing"] E --> F["Attacker sends ordinary HTTP
on the same connection"] F --> G["Forwarded verbatim to upstream,
skipping Echo router and middleware"] G --> H["Unprotected upstream resource
served, route auth bypassed"]

What can an attacker do?

The attacker reaches resources on the configured upstream that Echo's route layer would have protected, using the privileges of an unauthenticated client. The impact is whatever those resources expose without independent checks: internal endpoints, unauthenticated services, admin panels assumed to be shielded by the proxy's route middleware. The bounds, from the advisory:

  • No arbitrary-host proxying. The tunnel goes only to the upstream the proxy was configured for, so this is not an open proxy.
  • The upstream's own authentication still applies where it exists.
  • An accessible proxy route is required, and the upstream must reject the upgrade without closing the connection.
  • No authentication, user interaction or privileged position is required beyond network access to the route.

Given those bounds, a fair summary is: deployments that enforce auth at Echo and rely on the upstream being unreachable or unauthenticated behind it carry the real exposure, which is what the two severity ratings disagree about.

Why does the fix work?

The patch removes proxyRaw entirely and sends every request, WebSocket or not, through Go's reverse proxy. From the v4 fix commit:

-                // Proxy
-                switch {
-                case c.IsWebSocket():
-                    proxyRaw(tgt, c, config).ServeHTTP(res, req)
-                default: // even SSE requests
-                    proxyHTTP(tgt, c, config).ServeHTTP(res, req)
-                }
+                // ReverseProxy validates upgrades and closes both sides when a tunnel ends.
+                proxyHTTP(tgt, c, config).ServeHTTP(res, req)

proxyHTTP now normalizes ws and wss target schemes to http and https and delegates to httputil.NewSingleHostReverseProxy, which reads the upstream's response before committing: a tunnel is entered only after a valid 101 Switching Protocols answer, and a rejected upgrade is relayed as the ordinary HTTP response it is. As the release notes put it, rejected upgrades no longer leave a raw tunnel that bypasses Echo routing and route middleware.

The fix is complete for this issue at the middleware level: no path in Proxy any longer enters a tunnel without an accepted upgrade, and the patch also closes both sides when either disconnects, which is the companion advisory GHSA-9969-6676-49h5. What the fix does not do is add authentication to your upstream; resources that had no independent checks before remain exactly as open to correctly routed, authenticated traffic as they were.

How do I check if I were compromised?

No indicators of compromise are published for this issue, and Echo lists none. The request pattern an attacker would leave in logs is distinctive, so your own logs can answer it:

  • Connections to proxied routes with WebSocket upgrade headers, Connection: Upgrade and Upgrade: websocket, that continued to carry HTTP traffic after the upstream's non-101 response.
  • Multiple ordinary HTTP requests logged at the upstream from a single long-lived connection, where the front-line proxy logged no corresponding routed requests for the same traffic.
  • Any upstream access records showing requests reaching resources that the Echo route layer was supposed to gate.

If your upstream logs include the resource paths and your proxy logs include the routed paths, a mismatch between them for one client connection is the signal to investigate.

Timeline

  • 2026-10-22: Echo releases v4.16.1 and v5.4.1 with the fix, and maintainer vishr publishes advisory GHSA-cxf3-9h37-7rr8, rating it Moderate and crediting @Duodum.
  • 2026-10-22: The release notes for v4.16.1 and v5.4.1 describe the new validation and the companion security fixes.
  • 2026-10-23: The GitHub Advisory Database publishes the advisory under review as Critical, CVSS v4.0 9.1, with CWE-444 and CWE-863. Echo publishes v4.16.2 with follow-up hardening on the v4 line.
  • 2026-10-24: This post.

FAQ

Why do the severity ratings disagree?

The GitHub Advisory Database review scores it CVSS v4.0 9.1, Critical, with attack requirements flagged as present, while Echo's own advisory page labels it Moderate with no published score. The disagreement tracks the preconditions: the attack needs a proxy route, an upstream that rejects without closing, and upstream resources without their own checks. Where those hold, the advisory database scoring treats the route-level security boundary as fully defeated, which is what the fix urgency reflects.

Can this turn my server into an open proxy?

No. The tunnel connects only to the upstream the Proxy middleware was configured to forward to, and the advisory states the issue does not provide arbitrary-host proxying. The exposure is to your own upstream's resources, not to third-party destinations.

Does it bypass the upstream's own authentication?

No. The upstream's own auth mechanisms work exactly as before. What is bypassed is enforcement that lives only in Echo's routes and middleware, which is why the advisory's workaround is to put authorization on the upstream itself.

My service does not use Echo's Proxy middleware. Am I affected?

No. The bug lives in the Proxy middleware's raw WebSocket path. Applications that use Echo for routing, static files, or their own handlers, and mount no proxy, have none of this code path in use.

What came with the fix in v4.16.1 and v5.4.1?

Both releases bundle three companion advisories rated High in the database: GHSA-9969-6676-49h5, WebSocket proxy connections retained after a client disconnects; GHSA-h7wr-284f-5qvc, unbounded gzip expansion in the v4 Decompress middleware; and GHSA-f48p-c9v4-h7xv, unbounded BodyDump captures in v4. If you upgrade for the WebSocket fix, you close those as well.

Scan your Go services and container images with Ostorlab to identify the Echo module versions they carry and flag deployments still on v4.16.0 or v5.4.0 or older.

References

  • Echo advisory GHSA-cxf3-9h37-7rr8, rated Moderate by the vendor, published October 22, 2026, credit to @Duodum: https://github.com/labstack/echo/security/advisories/GHSA-cxf3-9h37-7rr8 (checked October 24, 2026)
  • GitHub Advisory Database review, Critical, CVSS v4.0 9.1, CWE-444 and CWE-863: https://github.com/advisories/GHSA-cxf3-9h37-7rr8 (checked October 24, 2026)
  • Fix commit on the v4 line, 3fb0ba7e: https://github.com/labstack/echo/commit/3fb0ba7e17645bb65611e2a08875d0a2f65c421f (checked October 24, 2026)
  • Fix commit on the v5 line, 1855061e: https://github.com/labstack/echo/commit/1855061e572ae9dab727c811d8382f266bfbaaec (checked October 24, 2026)
  • Release v4.16.1 notes, published October 22, 2026, including the Go 1.26 requirement and companion fixes: https://github.com/labstack/echo/releases/tag/v4.16.1 (checked October 24, 2026)
  • Release v5.4.1 notes, published October 22, 2026: https://github.com/labstack/echo/releases/tag/v5.4.1 (checked October 24, 2026)
  • Release v4.16.2 notes, published October 23, 2026: https://github.com/labstack/echo/releases/tag/v4.16.2 (checked October 24, 2026)
  • Vulnerable proxy.go source at v4.16.0: https://raw.githubusercontent.com/labstack/echo/v4.16.0/middleware/proxy.go (checked October 24, 2026)
  • pkg.go.dev importer count for echo/v4, 32,160 packages: https://pkg.go.dev/github.com/labstack/echo/v4 (checked October 24, 2026)
  • Companion advisory GHSA-9969-6676-49h5: https://github.com/advisories/GHSA-9969-6676-49h5 (checked October 24, 2026)
  • Companion advisory GHSA-h7wr-284f-5qvc: https://github.com/advisories/GHSA-h7wr-284f-5qvc (checked October 24, 2026)
  • Companion advisory GHSA-f48p-c9v4-h7xv: https://github.com/advisories/GHSA-f48p-c9v4-h7xv (checked October 24, 2026)