DEV Community

Cover image for Actuator Endpoints in Spring Boot: Allowlist, Don't Just Disable the Obvious Ones
Juan Torchia
Juan Torchia Subscriber

Posted on Originally published at juanchi.dev

Actuator Endpoints in Spring Boot: Allowlist, Don't Just Disable the Obvious Ones

A curl to /actuator/env on a Spring Boot backend with default configuration can return environment variables, system properties, and — in some versions and setups — datasource values. No credentials needed. No exploit needed. All it takes is nobody touching Actuator's security config after adding it to the pom.xml.

That's what I want to pick apart today: what Actuator exposes by default, which endpoints are structurally risky, and why the "I'll disable the ones that scare me" recipe is worse than having no strategy at all.

The real problem behind "actuator endpoints spring boot"

When someone googles "actuator endpoints spring boot," they're usually in one of two moments: they're adding the starter for the first time and want to know what turns on, or they're staring at a pentest/audit report that flagged an endpoint as exposed and need to understand why.

In both cases the underlying problem is the same: Actuator was built to give operational visibility — health checks, metrics, build info — but several of its endpoints return information that should never leave the internal network. The default configuration doesn't make that distinction. It distinguishes between "web-exposed" and "not," with criteria designed for development, not production.

My take is simple and not subtle: Actuator with default configuration is attack surface that gets overlooked all the time, and the right way to close it isn't turning off the endpoints that "sound dangerous" by gut feeling. It's defining an explicit allowlist of what gets exposed, with everything else closed by default.

What the official docs say (and what they don't)

The official Spring Boot Actuator documentation is clear on a point a lot of people don't read all the way to: since Spring Boot 2, only /health is exposed over HTTP by default. The rest of the endpoints exist but aren't web-exposed until you turn them on with management.endpoints.web.exposure.include.

That sounds reassuring. The problem shows up when a team, trying to solve an observability pain point, does what most tutorials show:

# Lo que copian de un tutorial sin pensarlo dos veces
management.endpoints.web.exposure.include=*
Enter fullscreen mode Exit fullscreen mode

That asterisk exposes every registered endpoint, including env, beans, configprops, heapdump, and threaddump. The docs do warn about it, but in a section separate from the one showing how to enable endpoints — and copy-paste habits don't respect section boundaries.

What the official docs don't say — because it's not their job to say it — is which combination of endpoints represents real risk in a system with production data. That's architecture judgment, not framework configuration. That's the gap this post is trying to close.

Where people get it wrong: the "I'll just disable the obvious ones" recipe

The most common recipe I see — and it makes sense as a first pass — goes like this: someone scans the list of endpoints, spots the ones that sound dangerous by name (env, shutdown, heapdump), and disables them one by one:

# Receta comun: deshabilitar lo que "suena" peligroso
management.endpoint.env.enabled=false
management.endpoint.shutdown.enabled=false
management.endpoint.heapdump.enabled=false
management.endpoints.web.exposure.include=*
Enter fullscreen mode Exit fullscreen mode

The hidden cost of this recipe is that it still starts from total exposure (include=*) and subtracts from there. Every new endpoint Spring Boot adds in a future version, every dependency that registers its own Actuator endpoint (some third-party libraries do), stays exposed by default until someone finds out and adds it to the blacklist.

The comparison I keep coming back to: it's the difference between a firewall that blocks known ports and one that only allows the ports you actually need. The first protects you from threats you already know about. The second protects you from the ones that don't exist yet either.

/env is the most-cited case because the damage is direct and easy to demonstrate: it returns the full PropertySource tree, which in real configs includes database credentials, tokens for external services, and app secrets if management.endpoint.env.keys-to-sanitize wasn't set (or whatever sanitization mechanism applies to your version). /heapdump is arguably worse: a full memory dump can contain strings with active session tokens, which connects directly to how to think about sessions and digital identity — if those sessions live in process memory, a leaked heapdump exposes them just as much as a token stolen through XSS.

The explicit allowlist: a decision matrix

Instead of starting from "everything open, subtract what scares me," the alternative is to start from "everything closed, add what I can justify":

# Allowlist explicita: arranca cerrado, se abre por necesidad
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=when-authorized
Enter fullscreen mode Exit fullscreen mode

On top of that minimal baseline, adding each extra endpoint becomes a case-by-case decision. Here's the matrix I use to evaluate each one before adding it to the allowlist:

Endpoint Exposed by default Risk if leaked Criteria
health Yes, on Boot 2+ Low (with show-details restricted) Leave open, but no details for unauthenticated users
info No Low Useful for build version; check it doesn't include sensitive metadata
env No High — can leak secrets and credentials Behind auth only, never public, with sanitization active
metrics No Medium — can leak internal topology Restrict to internal network or ops auth
heapdump No High — full process memory Never exposed over the web; local/SSH access only
shutdown No, and must be explicitly enabled Critical — kills the process Don't enable in production except behind a controlled orchestrator
loggers No Medium — allows changing log level at runtime Behind auth with an ops role

Every row in this table is a guideline, not an absolute rule: metrics can be perfectly public on a system with no sensitive data in metric tags, and health with full details can be fine if it only runs on the internal network. The point isn't to memorize the table. It's to ask yourself "what happens if someone unauthenticated sees this?" for every endpoint before it goes into include.

Protecting what you do expose with Spring Security

Once the allowlist is defined, the second common mistake is assuming "being on the allowlist" means "being protected." Spring Security lets you split Actuator's path off from the rest of the app and apply its own rules:

// Configuracion tipica: reglas distintas para actuator vs resto de la app
@Bean
public SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
    http
        .securityMatcher(EndpointRequest.toAnyEndpoint())
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(EndpointRequest.to("health", "info")).permitAll()
            .anyRequest().hasRole("OPS")
        );
    return http.build();
}
Enter fullscreen mode Exit fullscreen mode

EndpointRequest.to(...) is the matcher Spring Boot provides specifically for this — it saves you from hand-mapping Actuator paths and having them break every time management.endpoints.web.base-path changes. The combination matters: the allowlist defines what exists, Spring Security defines who can see it.

flowchart LR
  A[Request a /actuator/algo] --> B{¿Esta en el include?}
  B -->|no| C[404, no existe]
  B -->|si| D{¿Pasa Spring Security?}
  D -->|no| E[401/403]
  D -->|si| F[Respuesta del endpoint]
Enter fullscreen mode Exit fullscreen mode

Limits of this guide

This matrix is architecture judgment, not a measured result from a specific system. I don't have real incident metrics to cite, and I'm not going to make them up: there's no public evidence of concrete cases in this post beyond the official Spring Boot documentation linked above. What that source does let you state with confidence is the documented default behavior (only health exposed on Boot 2+, everything else needs explicit include) and the exposure mechanism via management.endpoints.web.exposure.

What you can't conclude without running your own experiment: the exact impact of exposing env on a system with your specific secrets, how sanitization behaves in your particular Boot version (it's changed across versions, so check the changelog for the one you're running), or whether a WAF/proxy in front already mitigates part of the risk before the request even reaches the app. If the goal is a formal audit, the sensible move is running an exposed-endpoints scanner against a staging environment, not assuming theory is enough.

FAQ

Does Actuator come enabled by default in a Spring Boot project?
The spring-boot-starter-actuator dependency does register the endpoints once added, but default web exposure on Boot 2+ is limited to /health. Everything else needs explicit management.endpoints.web.exposure.include.

Why is /env the most-cited endpoint in Actuator security discussions?
Because it returns the full tree of the process's property sources, and in real-world configs that includes variables with credentials or tokens if key sanitization wasn't turned on.

Is it enough to just disable env and heapdump individually?
Not as a long-term strategy. Any new endpoint (from Boot or from a third-party dependency) stays exposed by default if the baseline is still include=*. The allowlist flips that logic.

Is Spring Security mandatory to run Actuator in production?
Not at the framework level — Spring Boot doesn't force it. But without an authorization layer sitting in front of Actuator, anything listed in include is reachable by anyone who knows the URL, credentials or no credentials. That's a design gap in the default setup, not a documented vulnerability, and EndpointRequest exists precisely to close it without hand-rolling path matchers.

Is health with full details safe to expose publicly?
Depends on what's in those details. management.endpoint.health.show-details=when-authorized is the sensible choice when you can't guarantee only internal traffic reaches the endpoint.

How do I check which endpoints are exposed on an already-running environment?
A direct curl to /actuator (no sub-path) usually lists the active endpoints if discovery is enabled — which by itself is information worth reviewing before it's exposed.

Where I stand

If the criterion for deciding which Actuator endpoints to expose is "I'll disable the ones that sound dangerous," the system is going to end up exposed by the next endpoint Spring Boot adds, the next dependency that registers one, or the next dev who runs include=* copying an old tutorial. An explicit allowlist plus Spring Security in front of it isn't the easiest option to start with, but it's the only one that doesn't depend on someone remembering to update a blacklist.

The concrete next step, if this hits home for a real system: go check management.endpoints.web.exposure.include right now, not after the next pentest. And while you're at it, if the system keeps sessions or tokens in memory, check how exposed /heapdump is too — the connection with stateless JWT vs stateful sessions isn't a coincidence: Actuator's exposure surface and the identity model you picked end up talking about the same thing — how easy it is to steal a session without stealing a password.

This post is a configuration guide, not an audit of a specific system — if you want something closer to how I think about dev tools with deliberate limits, the piece on Cline in VS Code follows the same logic of "full capability by default, explicit restriction after."

Original source: Spring Boot Actuator Docs


This article was originally published on juanchi.dev

Top comments (0)