Introduction
A misplaced conditional check in Spring Boot's default security auto-configuration silently disables all authorization rules for Actuator endpoints. Any unauthenticated user can then pull environment secrets, heap dumps, and configuration properties with a single HTTP request. The flaw, tracked as CVE-2026-40976 with a CVSS 3.1 score of 9.1, affects Spring Boot versions 4.0.0 through 4.0.5 and was patched in the 4.0.6 release on April 23, 2026.
The vulnerability is subtle because it only shows up under a specific but realistic dependency combination: a servlet-based web application that relies on the default security filter chain, includes spring-boot-actuator-autoconfigure, and does not include the newer spring-boot-health module. This configuration commonly appears in custom Boot starters, slimmed-down container images, and applications that deliberately exclude health endpoints to avoid conflicts.
Technical Information
Root Cause
CVE-2026-40976 is classified as CWE-862 (Missing Authorization). It comes from the interaction between Spring Boot 4.0's modular restructuring of dependencies and its auto-configured security defaults.
In Spring Boot 4.0, health endpoint support moved out of spring-boot-actuator-autoconfigure and into a dedicated spring-boot-health module. The default web security configuration in ManagementWebSecurityAutoConfiguration.java used a ClassUtils.isPresent(...) check to find out whether the HealthEndpoint class was on the classpath. The intent was to grant unauthenticated access (permitAll()) to the health endpoint only when it existed, and to lock down everything else with anyRequest().authenticated().
The flaw was in the placement of that check. In the vulnerable code, the whole authorizeHttpRequests block sat inside the isPresent guard:
if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", getClass().getClassLoader())) { http.authorizeHttpRequests((requests) -> { requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); requests.anyRequest().authenticated(); }); }
When spring-boot-health was not on the classpath (so the HealthEndpoint class was absent), Spring Boot silently skipped the entire authorization configuration. The default filter chain was registered with no authorization rules at all, and none covered the Actuator path, so every exposed endpoint was reachable anonymously.
Exploitation Conditions
An application is exploitable only when all of these conditions are true at once:
| Condition | Description |
|---|---|
| Application Type | Must be a servlet-based web application |
| Security Configuration | Must have no Spring Security configuration of its own and rely entirely on the default web security filter chain |
| Actuator Dependency | The classpath must include spring-boot-actuator-autoconfigure |
| Health Dependency | The classpath must NOT include spring-boot-health |
If any single condition is false, the application is not vulnerable.
Attack Flow
An attack on a vulnerable application is straightforward:
-
Reconnaissance: The attacker identifies a Spring Boot application (often from response headers or error page formatting) and probes the
/actuatorbase path. On a vulnerable instance, this returns a JSON document listing every available Actuator endpoint, with no authentication required. -
Secret Exfiltration via
/actuator/env: A GET request to/actuator/envreturns the full applicationEnvironment, including resolved property values such as database passwords, API keys, and JWT signing keys. One request is enough for full credential compromise. -
Heap Dump Capture via
/actuator/heapdump: Requesting/actuator/heapdumptriggers a full JVM heap dump. The binary file contains live object state, in-memory credentials, and active session tokens, and offline analysis can reveal secrets that never appear in environment properties. -
Log Manipulation via
/actuator/loggers: The attacker can POST to/actuator/loggersto change logging levels at runtime. They can suppress security audit logs to cover their tracks, or turn on verbose logging to capture more of the sensitive data flowing through the application. -
Architecture Mapping via
/actuator/configprops:/actuator/configpropsreturns every configuration property, which exposes the application's architecture, its integration wiring, and the downstream service endpoints the attacker can target next.
Impact Summary
| Endpoint | Exposed Data | Business Impact |
|---|---|---|
/actuator/env | Database passwords, API keys, JWT signing keys | Full credential compromise and secret exfiltration |
/actuator/heapdump | Live JVM heap with in-memory credentials and session tokens | Deep data exposure enabling offline analysis |
/actuator/configprops | All configuration properties | Reveals application architecture and integration details |
/actuator/loggers | Logger configuration (read/write) | Attackers can suppress security logs or enable verbose capture |
The environment and heap dump endpoints alone are typically enough to take an attacker from anonymous network access to full credential compromise in a single request.
Patch Information
The Spring team fixed CVE-2026-40976 in Spring Boot 4.0.6, released on April 23, 2026. Andy Wilkinson wrote the fix, committed as SHA 874f6294b91da18367b8b5ab7b2fad3fa23cfba6, and it closed GitHub issue #50188 directly. The fix was also forward-ported to the 4.1.x line through issue #50190 (milestone 4.1.0-RC1).
The Servlet Fix
The fix in ManagementWebSecurityAutoConfiguration.java changes the nesting. The authorization lambda now always applies, and only the health-specific permitAll() matcher is conditional:
- if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", - getClass().getClassLoader())) { - http.authorizeHttpRequests((requests) -> { - requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); - requests.anyRequest().authenticated(); - }); - } + http.authorizeHttpRequests((requests) -> { + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { + requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + } + requests.anyRequest().authenticated(); + });
The change looks simple, but it matters a great deal. With requests.anyRequest().authenticated() moved outside the isPresent check, every request requires authentication whether or not the health module is available.
The Reactive Fix
The WebFlux counterpart in ReactiveManagementWebSecurityAutoConfiguration.java had the opposite problem. It had no ClassUtils.isPresent guard at all, so it always tried to call healthMatcher() and additionalHealthPathsMatcher(). The fix wraps only the health-specific permit rules in the presence check and leaves anyExchange().authenticated() applying in every case:
http.authorizeExchange((exchanges) -> { - exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { + exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + } exchanges.anyExchange().authenticated(); });
New Tests
The commit adds a dedicated test method, securesEverythingElseWhenHealthIsAbsent(), to both the servlet and reactive test suites. It uses @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") to reproduce the exact vulnerable configuration and confirms that all endpoints now return 401 Unauthorized. The existing tests were updated too, so that the HealthContributorAutoConfiguration, HealthContributorRegistryAutoConfiguration, and HealthEndpointAutoConfiguration classes are added explicitly, and only in tests that exercise health endpoint behavior.
The whole change is modest, 121 additions and 52 deletions across 4 files, but its security impact is large: it closes a full authorization bypass that left every endpoint unauthenticated.
Affected Systems and Versions
The vulnerability affects Spring Boot versions 4.0.0 through 4.0.5.
An application is vulnerable only if it meets all four of these conditions:
- It is a servlet-based web application.
- It has no Spring Security configuration of its own and relies entirely on the default web security filter chain.
- Its classpath includes
spring-boot-actuator-autoconfigure. - Its classpath does not include
spring-boot-health.
Applications on Spring Boot versions before 4.0.0 are not affected, because the health endpoint only moved into a separate module in the 4.0 line. Spring Boot 4.0.6 or later, and 4.1.0-RC1 or later, contain the fix.
Vendor Security History
The Spring Boot 4.0.6 release that patches CVE-2026-40976 also fixes three other vulnerabilities found in the same version range:
| CVE ID | Vulnerability |
|---|---|
| CVE-2026-40970 | Elasticsearch auto-configuration TLS hostname verification disablement |
| CVE-2026-40972 | DevTools remote secret comparison timing attacks |
| CVE-2026-40975 | Random value property source using weak PRNG |
| CVE-2026-40976 | Default security filter chain missing authorization rule |
Four security fixes in a single release indicate an active security maintenance cycle. The Spring team disclosed the vulnerability and shipped the patch on the same day (April 23, 2026), which shows a coordinated disclosure and rapid response process.
References
- NVD: CVE-2026-40976
- CVE Record: CVE-2026-40976
- Spring Security Advisory: CVE-2026-40976
- Spring Boot 4.0.6 Release Announcement
- Spring Boot 4.0.6 Release on GitHub
- GitHub Issue #50188
- Fix Commit 874f6294b91da18367b8b5ab7b2fad3fa23cfba6
- HeroDevs: Spring Boot 4.0 Actuator Authorization Bypass Analysis
- Snyk Vulnerability Database: SNYK-JAVA-ORGSPRINGFRAMEWORKBOOT-16191774
- Spring Boot Actuator Endpoints Documentation



