惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
Help Net Security
Help Net Security
Y
Y Combinator Blog
WordPress大学
WordPress大学
D
DataBreaches.Net
N
Netflix TechBlog - Medium
U
Unit 42
爱范儿
爱范儿
MyScale Blog
MyScale Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
D
Docker
H
Help Net Security
Stack Overflow Blog
Stack Overflow Blog
宝玉的分享
宝玉的分享
博客园_首页
Microsoft Security Blog
Microsoft Security Blog
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
P
Privacy & Cybersecurity Law Blog
P
Proofpoint News Feed
T
Tenable Blog
S
Schneier on Security
V
Vulnerabilities – Threatpost
V
V2EX
T
Tor Project blog
Security Latest
Security Latest
S
Securelist
G
Google Developers Blog
NISL@THU
NISL@THU
Schneier on Security
Schneier on Security
Webroot Blog
Webroot Blog
小众软件
小众软件
Google Online Security Blog
Google Online Security Blog
阮一峰的网络日志
阮一峰的网络日志
W
WeLiveSecurity
IT之家
IT之家
I
InfoQ
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
月光博客
月光博客
I
Intezer
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
Cloudbric
Cloudbric
Scott Helme
Scott Helme
The Cloudflare Blog
L
LINUX DO - 热门话题

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Spring Boot Actuator en producción: los endpoints que dejé abiertos sin darme cuenta y cómo los cerré
Juan Torchia · 2026-05-11 · via DEV Community

Spring Boot Actuator en producción: los endpoints que dejé abiertos sin darme cuenta y cómo los cerré

Estaba revisando la configuración de un backend Spring Boot 3.x que vengo construyendo — el mismo que discutí en el post de Jakarta EE vs Spring Boot — cuando hice algo que debería haber hecho desde el día uno: un curl simple contra /actuator.

La respuesta me cayó como un balde de agua fría.

{
  "_links": {
    "self":        { "href": "http://localhost:8080/actuator" },
    "beans":       { "href": "http://localhost:8080/actuator/beans" },
    "health":      { "href": "http://localhost:8080/actuator/health" },
    "info":        { "href": "http://localhost:8080/actuator/info" },
    "env":         { "href": "http://localhost:8080/actuator/env" },
    "loggers":     { "href": "http://localhost:8080/actuator/loggers" },
    "metrics":     { "href": "http://localhost:8080/actuator/metrics" },
    "mappings":    { "href": "http://localhost:8080/actuator/mappings" },
    "threaddump":  { "href": "http://localhost:8080/actuator/threaddump" },
    "heapdump":    { "href": "http://localhost:8080/actuator/heapdump" }
  }
}

Enter fullscreen mode Exit fullscreen mode

Diez endpoints. Sin autenticación. /actuator/env exponiendo variables de entorno. /actuator/heapdump sirviendo un dump completo del heap de la JVM a cualquiera que lo pidiera.

Momento de "espera, esto está abierto" en estado puro.

Mi tesis, después de investigar y cerrar todo esto, es directa: los defaults de Spring Boot Actuator son razonables para desarrollo local, pero son una trampa en producción, y la documentación oficial los presenta con un tono que suaviza el riesgo real. Si no configurás Actuator con intención, estás apostando a que nadie lo encuentre.


Spring Boot Actuator endpoints seguridad producción: qué queda expuesto por defecto

Spring Boot 3.x expone por defecto via HTTP solo dos endpoints: health e info. Pero eso es solo la mitad de la historia.

El problema está en que:

  1. /actuator (el índice) está habilitado y es público — enumera todo lo que existe.
  2. Si agregás spring-boot-starter-actuator sin configuración adicional, el índice revela los endpoints disponibles aunque no todos estén expuestos via HTTP.
  3. En entornos con management.endpoints.web.exposure.include=* — que es exactamente lo que aparece en mil tutoriales de "cómo monitorear tu Spring Boot app" — abrís todo el tablero de un saque.

Corrí un script de enumeración simple contra el backend en un entorno de staging configurado como producción-réplica:

#!/bin/bash
# Script de auditoría de Actuator — reproducible en cualquier Spring Boot 3.x
BASE_URL="http://localhost:8080"

echo "=== Enumerando endpoints Actuator ==="
curl -s "$BASE_URL/actuator" | python3 -m json.tool

echo ""
echo "=== Probando /actuator/env (puede exponer secrets) ==="
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/actuator/env")
echo "HTTP Status: $STATUS"

echo ""
echo "=== Probando /actuator/heapdump (dump completo del heap) ==="
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/actuator/heapdump")
echo "HTTP Status: $STATUS — Si es 200, hay un problema serio."

Enter fullscreen mode Exit fullscreen mode

Lo que encontré en el entorno con configuración descuidada (el famoso exposure.include=* copiado de un tutorial de Prometheus):

Endpoint HTTP Status Riesgo
/actuator/env 200 Crítico — expone variables de entorno, incluyendo keys parcialmente enmascaradas
/actuator/beans 200 Alto — revela toda la estructura interna de beans Spring
/actuator/heapdump 200 Crítico — dump del heap JVM descargable, puede contener secrets en memoria
/actuator/mappings 200 Medio — mapeo completo de todos los endpoints HTTP de la app
/actuator/threaddump 200 Medio — estado de todos los threads, útil para fingerprinting
/actuator/loggers 200 Medio — permite cambiar niveles de log en runtime via POST
/actuator/health 200 Bajo (con detalle deshabilitado)
/actuator/info 200 Bajo (con info mínima)

El /actuator/env es el que más me preocupó. Devolvía algo así:

{
  "propertySources": [
    {
      "name": "systemEnvironment",
      "properties": {
        "DATABASE_URL": {
          "value": "jdbc:postgresql://****:5432/mydb",
          "origin": "System Environment Property"
        },
        "JWT_SECRET": {
          "value": "******",
          "origin": "System Environment Property"
        }
      }
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

Spring enmascara los valores con ****** para propiedades que detecta como sensibles — pero la detección es por nombre. Si la variable se llama MY_SIGNING_KEY en vez de JWT_SECRET, el valor aparece en texto plano. No es una garantía, es una heurística.


El proceso de cierre: application.properties + Spring Security

Después de mapear el surface de ataque, armé el hardening en dos capas. La primera capa es configuración pura; la segunda, Spring Security.

Capa 1 — application.properties

# ============================================================
# Actuator — Hardening para producción
# ============================================================

# Solo exponemos los endpoints que necesitamos operacionalmente
management.endpoints.web.exposure.include=health,info,metrics

# El índice /actuator enumera los endpoints disponibles — lo cerramos
management.endpoints.web.exposure.exclude=beans,env,heapdump,threaddump,loggers,mappings,sessions

# Health: detalle solo para requests autenticados
management.endpoint.health.show-details=when-authorized
management.endpoint.health.show-components=when-authorized

# Info: solo exponemos lo que configuramos explícitamente
management.info.env.enabled=false
management.info.java.enabled=false
management.info.os.enabled=false

# Movemos Actuator a un puerto interno (no expuesto en el load balancer)
# Opcional pero recomendado si tu infraestructura lo permite
management.server.port=8081

# Deshabilitamos endpoints que no usamos aunque no estén expuestos via HTTP
management.endpoint.heapdump.enabled=false
management.endpoint.threaddump.enabled=false
management.endpoint.env.enabled=false
management.endpoint.beans.enabled=false

Enter fullscreen mode Exit fullscreen mode

La opción del puerto separado (management.server.port=8081) es la más limpia si la infraestructura lo permite. En Railway, por ejemplo, el puerto expuesto públicamente es el PORT env var — si Actuator corre en 8081 y solo exponés PORT al exterior, los endpoints de management quedan inaccesibles desde internet directamente.

Cubrí más sobre configuración JVM y Railway en Spring Boot en producción: lo que la documentación omite.

Capa 2 — Spring Security

La configuración de properties es necesaria pero no suficiente. Si Security está mal configurado, o si alguien toca esa config en el futuro sin contexto, todo puede reabrirse. La segunda capa es el cinturón de seguridad:

@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {

    @Bean
    public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {
        http
            // Aplicamos esta config solo a rutas de Actuator
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests(auth -> auth
                // Health e info son públicos — para health checks del load balancer
                .requestMatchers("/actuator/health/**").permitAll()
                .requestMatchers("/actuator/info").permitAll()
                // Métricas solo para usuarios con rol MONITORING
                .requestMatchers("/actuator/metrics/**").hasRole("MONITORING")
                // Cualquier otro endpoint Actuator requiere ADMIN
                .anyRequest().hasRole("ADMIN")
            )
            // Actuator no necesita CSRF — es API interna
            .csrf(csrf -> csrf
                .ignoringRequestMatchers("/actuator/**")
            )
            // Sin sesiones para Actuator — stateless
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }
}

Enter fullscreen mode Exit fullscreen mode

Este enfoque usa el patrón de múltiples SecurityFilterChain que Spring Boot 3.x recomienda. La chain de Actuator tiene su propia lógica y no interfiere con la seguridad del resto de la app.


Los gotchas que nadie menciona en los tutoriales

Después de cerrar todo y correr la auditoría de nuevo, encontré tres situaciones que me atraparon y que vale la pena documentar.

1. /actuator/health detallado rompe health checks del load balancer

Cuando configurás show-details=when-authorized, el endpoint /actuator/health devuelve 200 OK con body mínimo para requests no autenticados — lo cual es correcto. Pero algunos health checkers corporativos esperan ver "status": "UP" en el body y parsean el JSON. Verificá que el health checker que usás funcione con el body reducido:

{ "status": "UP" }

Enter fullscreen mode Exit fullscreen mode

Railway usa el status HTTP (200 = healthy), no el body — así que ahí no hay drama. Pero si venís de un setup con AWS ALB o un probe de Kubernetes que valida el body, probalo antes de deployar.

2. management.endpoint.X.enabled=false vs exposure.exclude — no son lo mismo

  • exposure.exclude saca el endpoint de la lista HTTP pero lo deja habilitado internamente (JMX, etc.)
  • enabled=false lo deshabilita completamente en todos los transports

Para endpoints como heapdump y env, usá ambos. La razón: si alguien en el futuro agrega una dependencia de monitoring que habilita JMX, un endpoint solo excluido de HTTP puede reaparecer.

3. El /actuator/loggers POST es una puerta de escritura

/actuator/loggers/{name} acepta POST para cambiar niveles de log en runtime. Si ese endpoint queda abierto, cualquier atacante puede subir el nivel de logging a TRACE y potencialmente generar logs enormes (disk exhaustion) o bajar niveles de seguridad a OFF. Cerrarlo no es opcional.


Comparativa del surface de ataque antes y después

Corrí el mismo script de auditoría antes y después del hardening. El resultado:

# Antes del hardening (con exposure.include=*)
Endpoints accesibles sin auth: 10
Endpoints con información sensible: 3 (env, beans, heapdump)
Endpoints con capacidad de escritura: 2 (loggers, shutdown*)

# Después del hardening
Endpoints accesibles sin auth: 2 (health básico, info)
Endpoints con información sensible accesibles sin auth: 0
Endpoints con capacidad de escritura accesibles sin auth: 0

Enter fullscreen mode Exit fullscreen mode

El shutdown endpoint merece mención especial: está deshabilitado por defecto en Spring Boot 3.x, pero si alguna vez lo habilitaste para testing y olvidaste revertirlo, es un POST /actuator/shutdown que mata la JVM. Lo verifico explícitamente en el script de auditoría.

Este tipo de superficie de ataque es relevante si estás pensando en seguridad end-to-end, incluyendo el cifrado de datos en tránsito — algo que exploré en más profundidad en el post de Themis vs Web Crypto API.


FAQ — Spring Boot Actuator seguridad producción

¿Cuáles son los endpoints de Actuator que Spring Boot expone por defecto via HTTP?

En Spring Boot 3.x, solo health e info están expuestos via HTTP por defecto. Sin embargo, si usás management.endpoints.web.exposure.include=* (común en setups de Prometheus o Grafana copiados de tutoriales), todos los endpoints disponibles se exponen de golpe. El índice /actuator siempre está visible y enumera lo que hay.

¿Qué información sensible puede exponer /actuator/env?

/actuator/env expone todas las fuentes de configuración de Spring: variables de entorno del sistema, propiedades de application.properties, propiedades de sistema JVM, y más. Spring enmascara valores cuyo nombre contiene palabras como password, secret o key, pero la detección es por convención de nombre — no es infalible. Variables con nombres no estándar pueden aparecer en texto plano.

¿Es suficiente con management.endpoints.web.exposure.exclude para proteger los endpoints?

No. exclude solo controla la exposición HTTP. Los endpoints siguen habilitados para otros transports (JMX) y siguen siendo descubribles si conocés el path directo. La protección completa requiere combinar exclude, enabled=false para los endpoints más críticos, y Spring Security para los que dejás abiertos.

¿Cómo protejo /actuator/health sin romper los health checks del load balancer?

Configurá management.endpoint.health.show-details=when-authorized. Requests sin auth reciben {"status":"UP"} o {"status":"DOWN"} con HTTP 200/503 — suficiente para la mayoría de los health checkers basados en status HTTP. Verificá que el tuyo no parsee el body antes de deployar.

¿Cuál es la mejor práctica para Actuator en una arquitectura de microservicios?

Mover Actuator a un puerto interno (management.server.port=8081) y no exponer ese puerto en el load balancer o ingress público. Las herramientas de monitoring (Prometheus, Grafana) acceden desde la red interna; los usuarios externos nunca tienen acceso directo. Combinado con Spring Security en ese puerto, el surface de ataque queda muy reducido.

¿/actuator/heapdump es tan peligroso como suena?

Sí. Un heap dump contiene el estado completo de la memoria de la JVM en el momento de la captura: objetos en memoria, strings, estructuras de datos internas. En una app que maneja tokens JWT, conexiones a base de datos o cualquier dato de sesión de usuario, un heap dump capturado por un atacante es esencialmente una filtración de datos. Deshabilitalo en producción salvo que lo necesités para debugging activo y bajo acceso controlado.


Conclusión: la documentación oficial suaviza el riesgo real

La documentación oficial de Spring Boot Actuator es clara en decir que "para producción, te recomendamos asegurarte de que solo los endpoints de health e info estén expuestos". Pero el tono es de recomendación, no de advertencia fuerte. Y los tutoriales virales de "integrar Prometheus con Spring Boot" pasan directo a exposure.include=* sin mencionar que eso abre el heapdump al mundo.

Mi punto después de armar este checklist: Actuator es una herramienta poderosa de observabilidad, pero viene configurada para conveniencia de desarrollo, no para resiliencia de producción. El costo de no auditarlo es alto; el costo de cerrarlo correctamente es bajo — una tarde de trabajo, dos archivos de configuración.

Si venés de leer el post de pnpm vs npm en mi monorepo o el de functional programming en TypeScript, ya sabés que mi approach es validar en escenarios concretos antes de dar recomendaciones. Acá aplica lo mismo: no confíes en el default. Corrés el script de auditoría, ves qué devuelve, y después decidís qué cerrar.

El checklist final que uso ahora para cualquier backend Spring Boot antes de ir a producción:

# Checklist Actuator pre-producción — Spring Boot 3.x
# 1. Verificar qué endpoints están expuestos
curl -s http://localhost:8080/actuator | python3 -m json.tool

# 2. Confirmar que /actuator/env devuelve 401 o 404
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/env

# 3. Confirmar que /actuator/heapdump devuelve 401 o 404
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/heapdump

# 4. Confirmar que /actuator/beans devuelve 401 o 404
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/beans

# 5. Verificar que health básico sigue respondiendo para el load balancer
curl -s http://localhost:8080/actuator/health

# Resultado esperado en producción:
# env → 401 o 404 ✓
# heapdump → 401 o 404 ✓
# beans → 401 o 404 ✓
# health → 200 con {"status":"UP"} ✓

Enter fullscreen mode Exit fullscreen mode

Si alguno de los primeros tres devuelve 200 sin autenticación, parás el deploy y lo corregís. No hay excusa.


Fuentes originales:


Este artículo fue publicado originalmente en juanchi.dev