fix(security): explicitly restrict Spring Boot Actuator endpoints in production config #87

Open
opened 2026-03-27 09:24:54 +01:00 by marcel · 1 comment
Owner

Security Issue — LOW / PRE-PRODUCTION CHECKLIST

Found in: backend/src/main/resources/application.yaml

The current state

management:
  health:
    mail:
      enabled: false

The application.yaml does not explicitly restrict which actuator endpoints are exposed. The only actuator-related entry is disabling the mail health indicator. SecurityConfig.java permits /actuator/health unauthenticated (correct, needed for Docker health checks) and requires authentication for everything else — but only if the endpoints are actually enabled.

Spring Boot's default in Boot 3+ is to expose only health over HTTP, which is good. However, this is an implicit default. It is not documented in the config, and a well-meaning developer adding management.endpoints.web.exposure.include=* for debugging could accidentally expose /actuator/heapdump in production without realizing it.

What /actuator/heapdump leaks: a full JVM heap dump containing every in-memory object — including the PostgreSQL password, the MinIO secret key, and every active Spring Session token.

The fix

Make the production-safe configuration explicit in application.yaml:

management:
  endpoints:
    web:
      exposure:
        include: "health"   # only the health probe — nothing else
  endpoint:
    health:
      show-details: never   # don't leak db/minio status to unauthenticated callers
  health:
    mail:
      enabled: false

And in application-dev.yaml (dev profile only), allow more for developer convenience:

management:
  endpoints:
    web:
      exposure:
        include: "*"        # full access in local dev is fine
  endpoint:
    health:
      show-details: always

Add an integration test that asserts sensitive endpoints return non-200 without ADMIN credentials:

@Test
void heapdumpRequiresAdminAuth() throws Exception {
    mockMvc.perform(get("/actuator/heapdump"))
            .andExpect(status().isUnauthorized());
}

@Test
void envEndpointNotExposedInProduction() throws Exception {
    mockMvc.perform(get("/actuator/env"))
            .andExpect(status().isNotFound()); // endpoint not enabled → 404
}

Why

Security through implicit defaults is fragile. One config change in a future PR can flip the exposure from safe to catastrophic. An explicit include: "health" means a PR that opens up more endpoints is visible in code review.

Priority

LOW for the current home-network deployment. HIGH before any public or internet-adjacent deployment. The fix is a two-line YAML change — do it early.

## Security Issue — LOW / PRE-PRODUCTION CHECKLIST **Found in:** `backend/src/main/resources/application.yaml` ### The current state ```yaml management: health: mail: enabled: false ``` The `application.yaml` does not explicitly restrict which actuator endpoints are exposed. The only actuator-related entry is disabling the mail health indicator. `SecurityConfig.java` permits `/actuator/health` unauthenticated (correct, needed for Docker health checks) and requires authentication for everything else — but only if the endpoints are actually enabled. Spring Boot's default in Boot 3+ is to expose only `health` over HTTP, which is good. However, this is an implicit default. It is not documented in the config, and a well-meaning developer adding `management.endpoints.web.exposure.include=*` for debugging could accidentally expose `/actuator/heapdump` in production without realizing it. **What `/actuator/heapdump` leaks:** a full JVM heap dump containing every in-memory object — including the PostgreSQL password, the MinIO secret key, and every active Spring Session token. ### The fix Make the production-safe configuration **explicit** in `application.yaml`: ```yaml management: endpoints: web: exposure: include: "health" # only the health probe — nothing else endpoint: health: show-details: never # don't leak db/minio status to unauthenticated callers health: mail: enabled: false ``` And in `application-dev.yaml` (dev profile only), allow more for developer convenience: ```yaml management: endpoints: web: exposure: include: "*" # full access in local dev is fine endpoint: health: show-details: always ``` Add an integration test that asserts sensitive endpoints return non-200 without ADMIN credentials: ```java @Test void heapdumpRequiresAdminAuth() throws Exception { mockMvc.perform(get("/actuator/heapdump")) .andExpect(status().isUnauthorized()); } @Test void envEndpointNotExposedInProduction() throws Exception { mockMvc.perform(get("/actuator/env")) .andExpect(status().isNotFound()); // endpoint not enabled → 404 } ``` ### Why Security through implicit defaults is fragile. One config change in a future PR can flip the exposure from safe to catastrophic. An explicit `include: "health"` means a PR that opens up more endpoints is visible in code review. ### Priority **LOW for the current home-network deployment. HIGH before any public or internet-adjacent deployment. The fix is a two-line YAML change — do it early.**
marcel added the security label 2026-03-27 12:29:51 +01:00
Author
Owner

Audit note (2026-05-07) — heightened urgency after CVE disclosure

Live trivy fs against backend/pom.xml confirms two CRITICAL Spring CVEs that intersect with this issue:

  • CVE-2026-22731 (HIGH) — spring-boot-starter-actuator@4.0.0: Authentication bypass via misconfigured Health Group additional path.
  • CVE-2026-22733 (HIGH) — spring-boot-starter-actuator@4.0.0: Authentication bypass under Actuator CloudFoundry endpoints.
  • CVE-2026-40976 (CRITICAL) — spring-boot@4.0.0: Default security filter chain has no authorization rule with Actuator.

Fixed in Spring Boot 4.0.6. A separate P0 issue will track the dependency bump itself.

The work in this issue (explicit management.endpoints.web.exposure allowlist, secure non-health endpoints) remains valid and complementary — even after the patch, the principle of "explicit, minimal exposure" is the defensive default.

Suggested AC additions

  • In application-prod.yaml: management.endpoints.web.exposure.include is an explicit allowlist (e.g., health,info,prometheus), never *.
  • /actuator/prometheus is allowed only for the scrape source (network ACL or token).
  • /actuator/health exposes a different shape externally (just UP/DOWN) than internally (with subsystem detail).

Tracked in audit doc as part of F-22 (Critical, escalated). See docs/audits/2026-05-07-pre-prod-architectural-review.md Appendix A.1.1.

## Audit note (2026-05-07) — heightened urgency after CVE disclosure Live `trivy fs` against `backend/pom.xml` confirms two **CRITICAL** Spring CVEs that intersect with this issue: - **CVE-2026-22731** (HIGH) — `spring-boot-starter-actuator@4.0.0`: Authentication bypass via misconfigured Health Group additional path. - **CVE-2026-22733** (HIGH) — `spring-boot-starter-actuator@4.0.0`: Authentication bypass under Actuator CloudFoundry endpoints. - **CVE-2026-40976** (CRITICAL) — `spring-boot@4.0.0`: Default security filter chain has no authorization rule with Actuator. Fixed in **Spring Boot 4.0.6**. A separate P0 issue will track the dependency bump itself. The work in this issue (explicit `management.endpoints.web.exposure` allowlist, secure non-`health` endpoints) remains valid and complementary — even after the patch, the principle of "explicit, minimal exposure" is the defensive default. ### Suggested AC additions - [ ] In `application-prod.yaml`: `management.endpoints.web.exposure.include` is an explicit allowlist (e.g., `health,info,prometheus`), never `*`. - [ ] `/actuator/prometheus` is allowed only for the scrape source (network ACL or token). - [ ] `/actuator/health` exposes a *different* shape externally (just `UP/DOWN`) than internally (with subsystem detail). Tracked in audit doc as part of **F-22** (Critical, escalated). See `docs/audits/2026-05-07-pre-prod-architectural-review.md` Appendix A.1.1.
Sign in to join this conversation.
No Label security
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#87