By the SurfaceCheckr team. Every finding maps to a check we run in production - see our methodology.

Spring Boot Actuator: the /actuator endpoints that dump your secrets

Spring Boot ships with a feature called Actuator. It's genuinely useful: a set of HTTP endpoints that report on the running app, its health, its config, its metrics, so your ops tooling can watch it. Plenty of teams turn it on and never think about it again.

Type /actuator/env after a Spring Boot app's URL and see what comes back. On a hardened install, a 401 or a 404. On a surprising number of live apps, a JSON document listing every environment variable and config property the app is running with: the full shape of your configuration, the active profiles, and every property key. Modern Spring Boot (3.x) masks every value to ****** by default (the show-values setting defaults to never), so the worst leak usually isn't the values here, it's the structure. The real catastrophe is one endpoint over, in the heap dump, where nothing is masked at all.

The endpoints, from bad to catastrophic

Actuator is a family of endpoints, and they're not equally dangerous. The order matters because it tells you what to check first.

The worst is /actuator/heapdump. It returns a binary snapshot of the JVM's entire memory at that moment. Not config, not a summary, the actual memory: every string the app is holding, which includes the database password in cleartext, every API key it loaded, active session tokens, JWTs mid-flight, and whatever user data happened to be in memory. A heap dump is the single richest file a Spring app can leak, because it contains secrets that were never written to any config file. Right behind it, /actuator/env and /actuator/configprops print your configuration: the database URL, every property key, and active profiles in plain sight, with secret-looking values masked to ****** by default but exposed in cleartext if show-values has been flipped on. Then /actuator/threaddump and /actuator/mappings leak your internal class structure and every route the app exposes, which is the map an attacker uses to plan the next move.

request
GET /actuator/env HTTP/1.1 Host: api.yoursite.com
response
HTTP/1.1 200 OK
Content-Type: application/json
{ "activeProfiles": ["prod"],
"spring.datasource.url": "jdbc:postgresql://db.internal:5432/app",
"spring.datasource.password": "******",
"jwt.secret": "******" }
Even with the default masking, the structure leaks: internal DB host, every config key, the active profile. Set show-values=always (a common misconfig) and the ****** become cleartext.

Why it's open when you didn't open it

The default changed, and a lot of apps predate the change or override it without realizing.

Older Spring Boot exposed many Actuator endpoints over HTTP by default. Newer versions only expose /actuator/health and /actuator/info unless you ask for more. (This is one of a whole family of debug and status endpoints left reachable in production.) But "ask for more" is one line of config, and it gets added all the time: a management.endpoints.web.exposure.include=* to make a metrics dashboard work, copied from a Stack Overflow answer, that now exposes everything. Or the app runs an old Boot version where the permissive default still holds. Either way the endpoints answer to anyone who can reach the app, and if the app is on the public internet, that's everyone. The jwt.secret leak is the quiet killer here: that value signs your session tokens, so an attacker who reads it (from the heap dump, or from /env if value-masking has been disabled) can forge a valid admin session without ever touching your login.

0:00Scanner pulls /actuator/heapdump
0:10Greps the dump for jwt.secret and DB password (unmasked)
0:40Forges a signed admin session token
1:30Logs in as admin, no password
The heap dump is the shortcut: nothing in it is masked, so the signing secret is right there. No brute force, no stolen password.

Lock the endpoints, then prove it from outside

The fix is to stop exposing Actuator endpoints over the public web, and to require auth on the few you do expose. This is config, not code.

# application.properties
management.endpoints.web.exposure.include=*
# ^ every endpoint, including env and heapdump, on the web
Expose only health and info publicly, secure the rest, and move Actuator to its own port if you can.

The strongest version puts Actuator on a separate management port bound to localhost or an internal network, so the sensitive endpoints aren't reachable from the internet at all, only from your own tooling. Whatever you keep on the public side should sit behind Spring Security with authentication. And /actuator/heapdump should never be web-reachable in production, full stop, because there's no safe way to serve a memory dump to the public.

Whether your /actuator/env, /heapdump, or the rest answer to an anonymous request is something a stranger checks by appending a path, which means we can check it the same way. SurfaceCheckr probes the known Actuator paths from outside, with no credentials, and flags any that return the telltale config JSON or heap-dump response instead of a 401. It reads what the endpoint serves and stops there; it won't pull your whole heap dump down, only confirm that an anonymous request gets one. That's the ten-second check that your Spring config isn't sitting on the open internet, and it's the kind of thing you only find by sending the request a stranger would.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.

External, read-only scan. We only request public URLs - never log in, never send attack traffic.