highCVE-2026-63188Aug 19, 2026

CVE-2026-63188: @logto/tunnel Path Traversal via Unsanitized request.url

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

The @logto/tunnel CLI serves local sign-in UI files without checking that the requested path stays inside the configured directory, so anyone who can reach the tunnel port can read arbitrary files on…

Package@logto/tunnel
Ecosystemnpm
Affected<= 0.3.8
Fixed in0.3.9
CVE-2026-63188: @logto/tunnel Path Traversal via Unsanitized request.url

The problem

The tunnel command accepts --experience-path to serve a local folder of static sign-in UI assets. In packages/tunnel/src/commands/tunnel/utils.ts, the filesystem path is built with path.join(staticPath, request.url) and opened with fs.open(requestPath, 'r').

No URL normalization and no containment check are performed before the file is opened. Because Node.js preserves dot-dot segments in request.url (for example /../secret.txt), the joined path can resolve to any location the CLI process can read. The server also binds to all interfaces by default, so the exposure is not limited to localhost.

Proof of concept

A working proof-of-concept for CVE-2026-63188 in @logto/tunnel, with the exact payload below.

http
GET /../secret.txt HTTP/1.1
Host: 127.0.0.1:9000

path.join('/tmp/logto-ui/static', '/../secret.txt') resolves to /tmp/logto-ui/secret.txt, which is outside the configured staticPath. Node preserves the leading /../ in request.url, so the traversal works without any encoding tricks.

The patch (commit 5686815) adds a path.resolve() call to canonicalize the joined path and then verifies that the result starts with the resolved staticPath before opening the file. Any request whose resolved path does not share the staticPath prefix is rejected with a 403.

CWE-22 applies directly: user-controlled input is fed to a filesystem operation with no boundary enforcement.

The fix

Upgrade @logto/tunnel to 0.3.9 or later. The fix is in commit 5686815955534f803d3d50738259efd0f741e62c (PR #9113). If you cannot upgrade immediately, bind the tunnel to localhost only and ensure the port is not reachable from other hosts.

Reported by wangsijie.

References: [1][2][3][4][5]

Related research