DEV Community

Cover image for Actuator endpoints en Spring Boot: allowlist, no deshabilitar lo obvio
Juan Torchia
Juan Torchia Subscriber

Posted on Originally published at juanchi.dev

Actuator endpoints en Spring Boot: allowlist, no deshabilitar lo obvio

Un curl a /actuator/env en un backend Spring Boot con configuración default puede devolver variables de entorno, propiedades del sistema y —en algunas versiones y configuraciones— valores de datasource. No hace falta credencial. No hace falta exploit. Hace falta que nadie haya tocado la configuración de seguridad de Actuator después de agregarlo al pom.xml.

Esto es lo que quiero desarmar: qué expone Actuator por defecto, qué endpoints son estructuralmente riesgosos, y por qué la receta de "deshabilito los que me dan miedo" es peor que no tener criterio ninguno.

El problema real detrás de "actuator endpoints spring boot"

Cuando alguien busca "actuator endpoints spring boot" en general está en uno de dos momentos: está agregando el starter por primera vez y quiere saber qué prende, o está mirando un pentest/auditoría que marcó un endpoint como expuesto y necesita entender por qué.

En los dos casos el problema de fondo es el mismo: Actuator nació para dar visibilidad operacional —health checks, métricas, info de build— pero varios de sus endpoints devuelven información que nunca debería salir de la red interna. La configuración por defecto no distingue eso. Distingue entre "web-exposed" y "no", con un criterio pensado para desarrollo, no para producción.

Mi tesis es simple y no es sutil: Actuator con la configuración default es superficie de ataque que se pasa por alto todo el tiempo, y la forma correcta de cerrarla no es apagar los endpoints que "suenan peligrosos" a ojo. Es definir una allowlist explícita de lo que se expone, y todo lo demás queda cerrado por default.

Qué dice la documentación oficial (y qué no dice)

La documentación oficial de Spring Boot Actuator es clara en un punto que mucha gente no lee hasta el final: desde Spring Boot 2, solo /health está expuesto por HTTP por defecto. El resto de los endpoints existen pero no están expuestos vía web hasta que los habilitás con management.endpoints.web.exposure.include.

Eso suena tranquilizador. El problema aparece cuando un equipo, buscando resolver un dolor de observabilidad, hace lo que la mayoría de los tutoriales muestran:

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

Ese asterisco expone todos los endpoints registrados, incluidos env, beans, configprops, heapdump y threaddump. La documentación lo advierte, pero en una sección separada de la que muestra cómo habilitar endpoints — y el patrón de copiar-pegar no distingue secciones.

Lo que la documentación oficial no dice —porque no es su trabajo decirlo— es qué combinación de endpoints representa riesgo real en un sistema con datos de producción. Eso es criterio de arquitectura, no configuración de framework. Ahí es donde entra la decisión que defiendo en este post.

Dónde se equivoca la gente: la receta de "deshabilito los obvios"

La receta más común que veo —y que tiene sentido en un primer approach— es esta: alguien revisa la lista de endpoints, identifica los que suenan peligrosos por nombre (env, shutdown, heapdump) y los deshabilita puntualmente:

# 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

El costo oculto de esta receta es que sigue partiendo de una exposición total (include=*) y resta desde ahí. Cada endpoint nuevo que Spring Boot agregue en una versión futura, cada dependencia que registre su propio endpoint de Actuator (algunas librerías de terceros lo hacen), queda expuesto por default hasta que alguien se entere y lo agregue a la lista negra.

El contraejemplo que suelo usar para explicar esto: es la diferencia entre un firewall que bloquea puertos conocidos y uno que permite solo los puertos que necesitás. El primero te protege de las amenazas que ya conocés. El segundo te protege también de las que todavía no existen.

/env es el caso más citado porque el daño es directo y fácil de demostrar: devuelve el árbol completo de PropertySource, que en configuraciones reales incluye credenciales de base de datos, tokens de servicios externos y secrets de aplicación si no se usó management.endpoint.env.keys-to-sanitize (o el mecanismo de sanitización correspondiente a la versión). Pero /heapdump es igual o más grave: un volcado de memoria completo puede contener strings con tokens de sesión activos, algo que conecta directo con cómo pensar sesiones e identidad digital — si esas sesiones viven en memoria del proceso, un heapdump filtrado las expone tanto como un token robado por XSS.

La allowlist explícita: matriz de decisión

En vez de partir de "todo abierto, resto lo que asusta", la alternativa es partir de "todo cerrado, agrego lo que necesito justificar":

# 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

Sobre esa base mínima, la decisión de agregar cada endpoint adicional se toma caso por caso. Esta es la matriz de criterio que uso para evaluar cada uno antes de sumarlo a la allowlist:

Endpoint Expone por defecto Riesgo si se filtra Criterio
health Sí, en Boot 2+ Bajo (con show-details restringido) Dejarlo abierto, pero sin detalles a usuarios no autenticados
info No Bajo Útil para versión de build; revisar que no incluya metadata sensible
env No Alto — puede filtrar secrets y credenciales Solo detrás de auth, nunca público, con sanitización activa
metrics No Medio — puede filtrar topología interna Restringir a red interna o auth de operaciones
heapdump No Alto — memoria completa del proceso Nunca expuesto vía web; solo acceso local/SSH
shutdown No, y requiere habilitarlo explícitamente Crítico — apaga el proceso No habilitarlo en producción salvo orquestador controlado
loggers No Medio — permite cambiar nivel de log en runtime Detrás de auth con rol de operaciones

Cada fila de esta tabla es un criterio, no una regla absoluta: metrics puede ser perfectamente público en un sistema sin datos sensibles en las etiquetas de métricas, y health con detalles completos puede ser aceptable si corre solo en red interna. El punto no es memorizar la tabla. Es hacerte la pregunta "¿qué pasa si esto lo ve alguien sin autenticar?" para cada endpoint antes de sumarlo al include.

Protegiendo lo que sí exponés con Spring Security

Una vez que la allowlist está definida, el segundo error común es asumir que "estar en la allowlist" es lo mismo que "estar protegido". Spring Security permite separar el path de Actuator del resto de la aplicación y aplicarle reglas propias:

// 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(...) es el matcher que Spring Boot provee específicamente para esto — evita tener que mapear paths de Actuator a mano y romperlos cada vez que cambia management.endpoints.web.base-path. La combinación importa: la allowlist define qué existe, Spring Security define quién puede verlo. Sin esa segunda capa, cualquier endpoint que esté en el include queda accesible a quien conozca la URL — no porque el framework lo obligue, sino porque nadie puso una capa de autorización delante.

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

Límites de esta guía

Esta matriz es criterio de arquitectura, no un resultado medido en un sistema específico. No tengo métricas de incidentes reales para citar, y no las voy a inventar: no hay evidencia pública de casos concretos en este post, más allá de la documentación oficial de Spring Boot enlazada arriba. Lo que sí se puede afirmar con esa fuente es el comportamiento default documentado (solo health expuesto en Boot 2+, el resto requiere include explícito) y el mecanismo de exposición vía management.endpoints.web.exposure.

Lo que no se puede concluir sin un experimento propio: el impacto exacto de exponer env en un sistema con secrets particulares, el comportamiento de sanitización en cada versión puntual de Boot (cambió entre versiones, así que conviene revisar el changelog de la versión en uso), o si un WAF/proxy delante ya mitiga parte del riesgo antes de que la request llegue a la aplicación. Si el objetivo es una auditoría formal, la recomendación prudente es correr un scanner de endpoints expuestos contra un ambiente de staging, no asumir que la teoría alcanza.

Preguntas frecuentes

¿Actuator viene habilitado por defecto en un proyecto Spring Boot?
El starter spring-boot-starter-actuator sí registra los endpoints al agregarlo, pero la exposición web por defecto en Boot 2+ se limita a /health. El resto necesita management.endpoints.web.exposure.include explícito.

¿Por qué /env es el endpoint más citado en discusiones de seguridad de Actuator?
Porque devuelve el árbol completo de fuentes de propiedades del proceso, y en configuraciones reales eso incluye variables con credenciales o tokens si no se activó la sanitización de claves.

¿Alcanza con deshabilitar env y heapdump puntualmente?
No como estrategia de largo plazo. Cualquier endpoint nuevo (de Boot o de una dependencia de terceros) queda expuesto por default si la base sigue siendo include=*. La allowlist invierte esa lógica.

¿Spring Security es obligatorio para usar Actuator en producción?
A nivel framework, no: Actuator arranca sin ninguna dependencia de Spring Security. Pero esa libertad tiene un costo directo: si un endpoint queda en el include y no hay ninguna capa de autorización delante, cualquiera que conozca la URL lo puede pegar sin credencial. No es una posibilidad remota, es el comportamiento por diseño cuando no se agrega nada más. EndpointRequest existe justamente para simplificar esa integración, no para cumplir un requisito formal del framework.

¿health con detalles completos es seguro de exponer públicamente?
Depende del contenido de esos detalles. management.endpoint.health.show-details=when-authorized es la opción prudente cuando no se puede garantizar que solo tráfico interno llegue al endpoint.

¿Cómo verifico qué endpoints están expuestos en un ambiente ya corriendo?
Un curl directo a /actuator (sin sub-path) suele listar los endpoints activos si discovery está habilitado, lo cual en sí mismo es información a revisar antes de exponerla.

Mi postura

Si el criterio para decidir qué endpoints de Actuator exponer es "deshabilito los que suenan peligrosos", el sistema va a quedar expuesto ante el próximo endpoint que Spring Boot agregue, la próxima dependencia que registre uno, o el próximo dev que ejecute include=* copiando un tutorial viejo. Allowlist explícita más Spring Security detrás no es la opción más cómoda para arrancar, pero es la única que no depende de que alguien se acuerde de actualizar una lista negra.

Lo incómodo de esto es que no requiere ningún exploit sofisticado: requiere que nadie haya vuelto a mirar la config después del día en que se agregó el starter. Esa es la parte que más me interesa señalar, no la lista de endpoints.

El próximo paso concreto, si esto resuena con un sistema real: revisar management.endpoints.web.exposure.include ahora mismo, no después del próximo pentest. Y de paso, si el sistema maneja sesiones o tokens en memoria, revisar también qué tan expuesto queda /heapdump — la conexión con JWT sin estado vs sesiones con estado no es casual: la superficie de exposición de Actuator y el modelo de identidad elegido terminan hablando de lo mismo, qué tan fácil es robar una sesión sin robar una contraseña.

Esta nota es guía de configuración, no auditoría de un sistema puntual — si buscás algo más cercano a cómo pienso herramientas de desarrollo con límites deliberados, la nota sobre Cline en VS Code sigue la misma lógica de "capacidad total por defecto, restricción explícita después".

Fuente original: Spring Boot Actuator Docs


Este artículo fue publicado originalmente en juanchi.dev

Top comments (0)