Sep 16, 2026·8 min read·11 visits
Unauthenticated path traversal vulnerability in http4s static services allows reading arbitrary files outside the resource base directory via percent-encoded path separators.
CVE-2026-69201 is a critical directory traversal vulnerability in the http4s Scala library. Affected versions of ResourceService and WebjarService allow attackers to escape the configured resource directory and access arbitrary files on the classpath or filesystem by using percent-encoded path separators. The flaw arises from decoding URL segments prior to validating them against directory escape patterns.
The vulnerability identified as CVE-2026-69201 is a directory traversal and path escape vulnerability existing within http4s, a popular type-safe, purely functional HTTP library for Scala. This security issue resides in the components responsible for serving static assets, specifically ResourceService and WebjarService. These components allow developers to mount folders from either the system filesystem, classpath, or packaged WebJar libraries and serve them directly over HTTP. In Scala applications built on functional principles, static asset resolution is typically handled by translating incoming URI paths into pure functional streams (fs2.Stream) that pull binary data from the underlying data store.
An unauthenticated remote attacker can exploit this vulnerability to read arbitrary files from the application classpath or underlying system filesystem, subject to the privileges of the JVM process. The flaw occurs because of a disconnect between how the URL-decoding phase operates and how the subsequent path validation phase enforces boundaries. When a request containing percent-encoded path separators is processed, it escapes the designed containment logic of the configured asset folder, allowing traversal past the designated root.
The attack surface is present whenever an application exposes classpath or file system directories using the affected services. Because static assets are typically public-facing, this vulnerability represents a significant risk to confidentiality. An attacker who successfully escapes the resource base directory can extract sensitive resources, configuration files, environment variables, or application binaries. This exposure is exacerbated in modern containerized deployments, where the JVM classpath may contain configuration secrets or internal microservice endpoints that are not intended for public access.
The root cause of CVE-2026-69201 is located in the sequence of operations used by http4s to process, decode, and validate path segments during static file retrieval. When an incoming HTTP request is received, http4s decomposes the request URI path into individual string segments. The static content serving engines then decode each individual segment to handle URL-encoded characters. This architectural decision—decoding path segments individually prior to path normalization—creates a gap where structural path boundaries are modified after the validation logic has been completed.
Once decoded, the application runs a path traversal validation routine to prevent directory escape. This verification is implemented via a fold operation over the collection of path segments, attempting to resolve each segment against a designated base directory. The validation checks if any decoded path segment is exactly equal to an empty string, a single dot, or a double dot (the parent directory reference).
segments.foldLeft(rootPath) {
case (_, "" | "." | "..") => throw BadTraversal
case (path, segment) => path.resolve(segment)
}The flaw manifests when an attacker supplies a path segment containing a percent-encoded path separator, such as %2F (forward slash) or %5C (backslash). Because the segment parser does not treat %2F as a path separator during initial segmentation, the sequence ..%2Fsecret.txt is treated as a single, indivisible path segment. The validation routine URL-decodes this segment to ../secret.txt before evaluating the pattern match.
When the decoded string ../secret.txt is evaluated against the pattern match, it fails to match "", ".", or "..". Consequently, it bypasses the security restriction and falls through to the default branch, where it is passed directly to the path.resolve(segment) API. Under the hood, Java's native file system path resolution (typically java.nio.file.Path) interprets the decoded slash character, executing a parent-directory traversal and allowing the attacker to escape the designated base directory. Because ClassLoaders or file system APIs interpret directory levels dynamically, resolving a segment containing .. and a slash effectively shifts the operational context upward, rendering the initial path constraints useless.
To fully understand the vulnerability, we can examine the specific patch introduced in commit bcd99cdc1342ef58a0f132acb814af06a668a8d3. The fix targets the pattern matching and validation logic inside the foldLeft path traversal check of all static services, including FileService, ResourceService, and WebjarService. The patch alters the traversal check by verifying whether the decoded segment contains any raw path separator characters.
Here is a visual representation of how the exploit bypasses the original validation mechanism compared to the patched logic:
The patched code introduces a guard condition inside the foldLeft processing block. In the fixed version of FileService.scala, ResourceService.scala, and WebjarService.scala, the segment validator is updated to reject any segment containing a slash or backslash character. This prevents the execution of directory traversal commands even when they are nested inside a longer string segment:
// Patched Validation Logic
segments.foldLeft(rootPath) {
case (_, "" | "." | "..") => throw BadTraversal
case (_, segment) if segment.contains("/") || segment.contains("\\") =>
throw BadTraversal
case (path, segment) => path.resolve(segment)
}This secondary check is critical because it neutralizes the bypass. If the decoded segment is ../secret.txt, the first case block is skipped, but the second case block matches because the string contains a / character. The system then immediately aborts execution and throws BadTraversal, preventing the native filesystem APIs from ever receiving the unsanitized path traversal sequence.
It is worth noting that while FileService was not directly exploitable in most configurations due to underlying file system constraints, the maintainers applied the patch to all three static content services. This design symmetry ensures that future changes to filesystem adapters or routing backends do not inadvertently reintroduce the path escape vector.
Exploiting CVE-2026-69201 requires that the target application serves static content and that the deployment environment does not normalize or strip encoded path separators before passing the request to the JVM backend. Some upstream reverse proxies or application gateways may automatically decode %2F to / prior to routing, which would cause http4s to segment the path normally and trigger the standard BadTraversal exception. However, if the proxy is configured to pass the raw URI or if the backend preserves percent-encoded characters, the system is fully vulnerable.
An attacker can construct a payload targeting sensitive files. For Unix-based environments, a GET request targeting /etc/passwd can be formatted as follows:
GET /static/..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1
Host: target.local
Accept: */*If the target application is running on Windows, the attacker can leverage percent-encoded backslashes (%5C) to traverse directories. This is particularly relevant because Windows path resolution supports both forward and backward slashes as directory separators:
GET /static/..%5C..%5C..%5CWindows%5Cwin.ini HTTP/1.1
Host: target.local
Accept: */*In addition to filesystem access, because ResourceService resolves assets against the classpath, an attacker can extract compiled .class files, application configuration files containing API keys, database credentials (such as application.conf or logback.xml), and other sensitive build artifacts. This makes the vulnerability highly critical in environments that package secret keys or credentials inside their classpath resources.
The absence of public exploit tools or active in-the-wild exploitation reports should not lull security teams into complacency. Due to the high ease of exploitation once the precise path layout is known, automated scanners can easily adapt standard directory traversal payloads to target this flaw. This highlights the importance of proactive defense.
The primary remediation for CVE-2026-69201 is to upgrade the http4s dependency to a secure version. For applications running on the 0.23.x release line, the dependency must be updated to at least 0.23.35. For applications utilizing the 1.0.0 milestone releases, the dependency must be updated to 1.0.0-M47 or higher. The dependency update is typically applied in the SBT build file:
// build.sbt update
libraryDependencies += "org.http4s" %% "http4s-blaze-server" % "0.23.35"If an immediate library upgrade is not feasible, organizations can implement several mitigation strategies at the infrastructure level. The most effective workaround is to configure upstream reverse proxies or Web Application Firewalls (WAFs) to inspect and block requests containing encoded path separators. For example, in an NGINX configuration, administrators can enforce strict URI normalization or reject requests containing %2F or %5C using specific rule definitions:
# NGINX block to reject percent-encoded path separators
if ($request_uri ~* "(%2F|%5C)") {
return 400;
}For environments utilizing Apache ModSecurity, a custom rule can be deployed to inspect the raw request URI and deny access when encoded traversal characters are detected:
SecRule REQUEST_URI_RAW "(?i:%2f|%5c)" "id:1000001,phase:1,deny,status:400,msg:'Blocked Path Traversal Attempt'"Additionally, developers should ensure that application runtimes do not execute with excessive privileges. Restricting the JVM user account to the minimum necessary file permissions will prevent the process from reading sensitive files outside the application context, even if a path traversal vulnerability is successfully exploited.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
http4s http4s | < 0.23.35 | 0.23.35 |
http4s http4s | >= 1.0.0-M1, < 1.0.0-M47 | 1.0.0-M47 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 5.9 (Medium) |
| Exploit Status | None (No active exploits observed) |
| CISA KEV Status | Not Listed |
| Impact | Confidentiality High (Unauthenticated Arbitrary File Read) |
The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as '..' that can resolve to a location outside of the restricted directory.
An OS command injection and argument injection vulnerability exists in the Nx build system and its official Maven (@nx/maven) and Gradle (@nx/gradle) plugins. When constructing the project dependency graph, the plugins invoke underlying Java build wrappers via subshells while interpolating unsanitized plugin options directly from nx.json into the command arguments. An attacker who supplies a crafted nx.json file via a repository or pull request can execute arbitrary shell commands with the privileges of the user or CI/CD runner invoking any Nx command that computes the project graph.
AdminNeo versions 4.6.2 through 5.4.1 contain an improper input validation vulnerability (CWE-20) in the version verification endpoint (?script=version). An unauthenticated remote attacker can submit a crafted HTTP POST request containing an array parameter for 'version', which is serialized into a shared server-side temporary cache file without type checking or authentication. On subsequent requests, the application deserializes this cached array and passes it to cookie() and rawurlencode(), triggering an uncaught fatal TypeError in PHP 8.x environments and causing a persistent 24-hour Denial of Service (DoS) for all users.
Charmbracelet Soft Serve versions 0.5.0 through 0.12.2 contain an authorization bypass and observable response discrepancy vulnerability in the HTTP Git server. Remote attackers can determine the existence, exact names, and clone URLs of private repositories by issuing crafted Go remote import path discovery requests (?go-get=1) or by observing differential HTTP status codes across Git Smart HTTP, Dumb HTTP, and Git LFS endpoints.
A Time-of-Check to Time-of-Use (TOCTOU) race condition in Charmbracelet Soft Serve versions 0.6.0 through 0.12.2 allows unauthenticated users to retain read access to repositories after their visibility is changed from public to private. The flaw stems from premature cache invalidation in the SetPrivate method prior to committing the visibility update to the underlying database. Concurrent read requests arriving during the database transaction window repopulate the non-expiring in-memory LRU cache with stale public metadata, bypassing access controls across HTTP, SSH, and Git protocols until the server process is restarted.
A resource consumption vulnerability exists in the Authlib joserfc Python library prior to version 1.7.5. When processing JSON Web Encryption (JWE) objects formatted using General JSON Serialization, the library fails to enforce an upper bound on the number of elements within the recipients array. An unauthenticated attacker can supply a JWE containing thousands of recipient entries targeting an RSA key management algorithm, forcing the server to perform an equivalent number of computationally expensive RSA private-key decryption operations per request and exhausting available CPU resources.
CVE-2026-119284 (GHSA-9pg5-8c7x-5hfx) is an uncontrolled resource consumption vulnerability (CWE-400) in the compact serialization deserialization routines of joserfc prior to version 1.7.6. The library enforced individual segment size limits only after invoking Python's bytes.split(b'.') on untrusted input tokens. By sending oversized compact JWS or JWE tokens, an unauthenticated remote attacker can force the Python interpreter to duplicate and allocate memory for every segment before size validation executes, resulting in memory exhaustion and an Endpoint Denial of Service (DoS).