Skip to content

Actuator exposed to the internet

Severity: criticalApplies to: Spring Boot 2.x+Applies to: Spring Boot 4.1Facts last verified 2026-07-26 against Spring Boot 4.1.0 · Spring Security 7.1.0

An exposed Actuator is not a vulnerability. Every part of it is a documented feature working exactly as designed, and Spring Boot’s defaults are deliberately conservative — only /health over HTTP, values sanitized, show-details: never.

Which makes the interesting question not “what is the bug” but how does a well-defaulted system end up open. There are two routes, and neither looks like a security decision at the time.

Someone needs Prometheus scraping, or a Kubernetes probe, or an APM agent. The endpoint they need is not exposed. The fastest thing that works is:

management.endpoints.web.exposure.include: "*"

It ships, monitoring works, and nobody returns to narrow it. Boot’s documentation warns about exactly this property, and the warning is easy to skip past when you are debugging a scrape target at the time.

Route two: the migration — the one worth understanding

Section titled “Route two: the migration — the one worth understanding”

This is the route that gets missed, because the change that causes it is a security change.

Spring Boot secures Actuator for you, but only conditionally. From its documentation:

If Spring Security is on the classpath and no other SecurityFilterChain bean is present, all actuators other than /health are secured by Spring Boot auto-configuration. If you define a custom SecurityFilterChain bean, Spring Boot auto-configuration backs off and lets you fully control the actuator access rules.

Now recall that WebSecurityConfigurerAdapter was removed in Spring Security 6.0. Every application migrating to 6 or 7 must define a SecurityFilterChain. The moment it does, Boot’s Actuator protection backs off — and a chain written from a form-login tutorial says nothing about /actuator/**.

A mandatory security migration silently removes a protection somewhere else. No warning, no failing test, no compile error. If both routes happen in the same application — the wildcard for monitoring, then a migration that drops the protection — the endpoints are public.

verify it workedRun this in: http response
Terminal window
curl -s -o /dev/null -w 'env=%{http_code} ' https://example.com/actuator/env
curl -s -o /dev/null -w 'heapdump=%{http_code}\n' https://example.com/actuator/heapdump

Run it unauthenticated, from outside your network. 401, 403 or 404 are all fine. A 200 on /actuator/heapdump is an incident, not a finding.

Roughly in order of severity:

/heapdump — a dump of the JVM heap, downloadable as a file. Every secret the process has in memory is in it: datasource passwords, API tokens, session contents, decrypted configuration. No sanitization setting applies, because sanitization operates on configuration properties, not on memory. This alone justifies the critical rating.

/env and /configprops — the shape of the configuration. Values are ****** by default, but the keys are not, and they map the application: which database, which message broker, which internal services, which cloud provider.

/loggerswritable. A POST changes log levels at runtime. That is a denial-of-service lever, and a way to make the application start logging data it normally would not.

/mappings and /beans — a complete route list and object graph, which turns black-box probing into reading a map.

/threaddump — stack traces with arguments, which regularly contain tokens.

Note what is mostly not here: this is a disclosure and control-plane compromise rather than direct code execution. There is no documented path from an exposed Actuator to running arbitrary code on the host, the way there is with a writable data store that can be made to drop a file on disk. It is rated critical on the heap dump alone, and inflating it into RCE would be the easy mistake.

Three layers, in order of durability:

  1. Do not expose what you do not need. Name endpoints; never *. See exposure.
  2. Say something explicit in your SecurityFilterChain, using EndpointRequest, because Boot has stopped saying it for you.
  3. Move Actuator to its own port with management.server.port and keep that port off the internet. This is the only layer that still holds when someone adds a wildcard in six months.

The third is the one to argue for. The first two depend on every future change being made carefully; the third does not.