On June 8, 2026, the Spring team published CVE-2026-41843: a path traversal in how Spring MVC and WebFlux resolve static resources when versioned resource support is on. The advisory rates it Medium, and the Spring team found it internally. The exploit lets a request resolve files outside the configured locations (CWE-22).
It is a good excuse to write down what a safe file-serving path looks like in each framework, because the fix is the same idea everywhere.
What's affected
All four conditions in the advisory must be true:
- The application uses Spring MVC or Spring WebFlux.
- It serves static resources from the file system.
- It has versioned resources support configured.
- The attacker knows or can guess metadata of the targeted resources.
| Line | Affected | Open source fix |
|---|---|---|
| 7.0.x | 7.0.0 to 7.0.7 | 7.0.8 |
| 6.2.x | 6.2.0 to 6.2.18 | 6.2.19 |
| 6.1.x | 6.1.0 to 6.1.27 | 6.1.28 (Enterprise Support only) |
| 5.3.x | 5.3.48 and earlier | 5.3.49 (Enterprise Support only) |
Enterprise Support builds also exist for 7.0.7.1 and 6.2.18.1. The advisory lists no workaround and says no further mitigation is needed once you are on a fixed version.
How to check if you're exposed
# Maven
mvn dependency:tree -Dincludes=org.springframework:spring-webmvc,org.springframework:spring-webflux
# Gradle
./gradlew dependencies --configuration runtimeClasspath | grep -E "spring-(webmvc|webflux)"
# Do you configure versioned resources on a file system location?
grep -rnE "VersionResourceResolver|resourceChain|addResourceLocations\(\"file:|spring\.web\.resources\.chain" src
If you find a file: location together with a version resolver, you meet the conditions. Spring Boot users can look for spring.web.resources.chain.strategy and spring.web.resources.static-locations entries in application.properties or application.yml.
What to do now
- Move to Spring Framework 7.0.8 or 6.2.19. If you are on 6.1.x or 5.3.x, the fixes are Enterprise Support releases, so plan a move to a supported open source line.
- If you cannot upgrade this week, check whether you need file system static locations. Serving the same files from the classpath or a CDN removes one precondition.
- Add a request test that asks for
../sequences (plain and encoded) against every static route. - Review other places that join user input to a base directory, using the patterns below.
How this could have been avoided
- Serve static files from a CDN or object store rather than the application's own file system.
- Do not combine file system locations with features that parse metadata out of the path unless the feature was written for that.
- Run a traversal test in CI against the real handler, not a unit test of the sanitizer.
- Keep the process user's file permissions narrow, so an escape reaches nothing sensitive.
The same fix in other frameworks
The core rule: resolve the final path, then confirm it is still inside the base directory. Do not rely on stripping ../ strings.
Flask. Use send_from_directory, which joins the name to the directory and answers 404 for a path that escapes it. Avoid passing user input to send_file.
@app.route("/files/<path:name>")
def files(name):
return send_from_directory(app.config["FILES_DIR"], name)
Node.js. Resolve and compare, with a separator so /data/public2 does not match /data/public:
const path = require("node:path");
const BASE = path.resolve("/data/public") + path.sep;
function safePath(name) {
const full = path.resolve(BASE, name);
if (!full.startsWith(BASE)) throw new Error("outside base");
return full;
}
Symlinks inside the base directory can still point out of it, so also call fs.realpath on the result and compare again if users can upload files.
Go. Go 1.24 added os.Root, a directory handle whose methods refuse names that leave it. The Go team says it covers .. components, symlinks that point outside the root and renames that race with the check. os.OpenInRoot is the shortcut for one untrusted name:
f, err := os.OpenInRoot("/srv/public", untrustedName)
The Go team lists limits: it does not stop traversal through bind mounts, and creating symlinks and renaming files are not supported yet.
Java without a framework fix. Normalize and compare using Path:
Path base = Path.of("/srv/public").toRealPath();
Path target = base.resolve(userInput).normalize();
if (!target.startsWith(base)) throw new SecurityException("outside base");
Why it matters
Path traversal bugs keep appearing in mature frameworks because the rule is easy to state and easy to get wrong at the edges: encoded separators, metadata parsed from the name, symlinks and platform differences. The Spring precondition list makes this plain. The bug needs a specific feature combination, and most deployments do not have it, but the ones that do cannot see that from the outside.
Prefer APIs that make the safe path the only path: a handle bound to a root directory, a framework helper that returns 404, or no local file system at all.
Quick check
Which four conditions must hold for CVE-2026-41843?
Spring MVC or WebFlux, static resources served from the file system, versioned resources support configured, and an attacker who knows or can guess the target resource metadata.
Why compare against the base path plus a separator in the Node example?
Without the trailing separator, a sibling directory such as /data/public2 would pass a startsWith check against /data/public.