The forgotten /actuator endpoint: Spring Boot info disclosure in 2026
Spring Boot ships with the spring-boot-starter-actuator dependency in just
about every project template, scaffold, and tutorial since 2017. Its purpose is internal
operations: health checks, metrics, request traces, JVM introspection, runtime config
inspection. None of which should be reachable from the public internet. All of which
frequently are.
The default config trap
Pre-Spring Boot 2.0, all actuator endpoints were exposed by default. Devs complained.
From 2.0 onwards, only /actuator/health and /actuator/info are
exposed by default. So, "fixed", right?
Then someone, somewhere, in pursuit of a metrics dashboard, adds:
# application.properties
management.endpoints.web.exposure.include=*
And now /actuator/env, /actuator/heapdump,
/actuator/threaddump, /actuator/loggers,
/actuator/configprops, /actuator/httptrace... are all
reachable. From the open internet. With no authentication.
We've found this in production at companies you've heard of. Two CRITICAL hits in our
scan database came from /actuator/heapdump directly serving JVM heap dumps
to anonymous requests. A heap dump contains every secret, every session, every credential
the JVM knew at dump time.
The bad endpoints, ranked
| Endpoint | What it leaks | UnveilScan severity |
|---|---|---|
/actuator/heapdump | Full JVM heap (binary, ~100 MB) | CRITICAL |
/actuator/env | Every env var + Spring property + DB URL | CRITICAL |
/actuator/threaddump | Thread states, stack traces with method args | HIGH |
/actuator/configprops | Bound configuration class fields | HIGH |
/actuator/loggers | Logger names + levels (POST can change them — log injection) | HIGH |
/actuator/httptrace | Last 100 HTTP requests with full headers (cookies, auth) | HIGH |
/actuator/mappings | Every controller endpoint + parameters | MEDIUM |
/actuator/health | Service status — varies by config | LOW (often expected) |
How attackers use this
- Find a Spring Boot app via the
X-Application-Contextheader or paths like/actuator/healthreturning{"status":"UP"}. - Probe
/actuator/env. If 200 OK with JSON body, gold mine: read DB URLs, S3 keys, JWT secrets, API tokens. - Probe
/actuator/heapdump. If 200 with binary body, download. Open in Eclipse MAT or VisualVM. Search for "password", "secret", "token". Find session tokens still active. - Probe
/actuator/loggers. POST a config change to log the next 100 requests at TRACE level. Now you log all your own traffic to your own attacker-controlled appender.
Fix in 3 lines
# application.properties
# Only the bare minimum — health for load balancers, info for ops dashboards.
management.endpoints.web.exposure.include=info,health
# Bind actuator to a different port (loopback only, behind firewall)
management.server.port=8081
management.server.address=127.0.0.1
Or in application.yml:
management:
endpoints:
web:
exposure:
include: info,health
server:
port: 8081
address: 127.0.0.1
The actuator listener now binds 127.0.0.1:8081. External traffic on 80/443
can't reach it. Internal monitoring (Prometheus scraper on same host) still can.
Authenticating actuator
If you genuinely need actuator from outside the box (a remote monitoring service), add Spring Security:
@EnableWebSecurity
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
.and().httpBasic();
}
}
Plus spring.security.user.name=... + a strong password — or, better, OAuth.
How UnveilScan checks this
Our api_surface checker probes 14 paths including /actuator,
/actuator/env, /actuator/heapdump, plus equivalents for other
frameworks (graphql, swagger, openapi). Severity scaled by what we find:
/actuator/envreachable + 200 → CRITICAL (env vars in clear)/actuator/heapdumpreachable → CRITICAL/actuator/threaddump,/actuator/httptrace→ HIGH/actuator/health,/actuator/info→ INFO (typically expected)
A canary __unveilscan_canary_nx probe rules out catch-all 200s before
flagging anything. False-positive rate is ~0%.
Probe your /actuator surface
Free Basic scan reports the obvious endpoints. Extended adds 5 more probes.
Scan a domain