security(deps): bump Spring Boot to 4.0.6 to clear 2 CRIT + 17 HIGH CVEs #457
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Pre-prod audit (
docs/audits/2026-05-07-pre-prod-architectural-review.mdAppendix A.1.1) rantrivy fsagainstbackend/pom.xmland found 2 CRITICAL + 17 HIGH vulnerabilities, all introduced via the Spring Boot 4.0.0 BOM. Spring Boot 4.0.6 is the lowest version that resolves the criticals; it also pulls patched Netty, Jetty, Jackson, and Spring Security as transitives.The two criticals are not theoretical — both are pre-auth bypass paths in framework code we use:
spring-boot@4.0.0: default security filter chain has no authorization rule with Actuator. Fixed in 4.0.6.spring-security-web@7.0.0: security policy bypass + information disclosure. Fixed in 6.5.9 / 7.0.4.Plus 17 HIGHs covering Netty request smuggling (CVE-2026-33870), HTTP/2 CONTINUATION-flood DoS (CVE-2026-33871), Spring Security path-matching bypass (CVE-2026-22753, CVE-2026-22754), Spring Boot temp-dir ownership (CVE-2026-40973), Actuator auth bypasses (CVE-2026-22731, CVE-2026-22733), Jetty smuggling (CVE-2026-2332), pgjdbc client DoS (CVE-2026-42198), Jackson DoS (CVE-2026-29062), and more.
Cross-references: complementary to #87 (explicit Actuator restriction).
Approach
Single-line
<version>bump in the parent POM, then verify viatrivy fsand the existing test suite.Critical files
backend/pom.xml—<parent><artifactId>spring-boot-starter-parent</artifactId><version>4.0.0</version>→4.0.6backend/pom.xml— also bump<dependency>com.googlecode.owasp-java-html-sanitizer</dependency>to20260101.1(CVE-2025-66021, not in the Boot BOM)Verification
trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xmlshould report 0 CRITICAL and ≤2 HIGH (whatever isn't yet patched upstream)../mvnw clean verifypasses with 88% JaCoCo branch-coverage gate intact.docker compose up -d, login as admin, hit/api/users/me,/api/documents,/api/documents/search?q=walter— all 200../mvnw test -Dtest='*ArchTest').cd frontend && npm run generate:api && npm run check.Acceptance criteria
4.0.6(or later if released by merge time).20260101.1.trivy fs backend/pom.xmlreports 0 CRITICAL.docker compose up -dhealthy across all services.npm run generate:api && npm run checkclean (no new type errors).Effort
S — 30 minutes including verification. Highest-value-per-minute change in the entire roadmap.
Risk if not addressed
Pre-auth bypass on Actuator endpoints (CVE-2026-40976), security-policy bypass via Spring Security (CVE-2026-22732), HTTP request smuggling (CVE-2026-33870, CVE-2026-2332), HTTP/2 DoS (CVE-2026-33871). All are publicly disclosed and exploitable.
Tracked in audit doc as F-22 (Critical, escalated from Medium after dynamic scan).
🏗️ Markus Keller — Application Architect
Observations
SecurityConfig.javaalready restricts Actuator correctly:/actuator/healthis the only endpoint exposed without auth. The issue body references CVE-2026-40976 (Actuator auth bypass in the default filter chain) — the existingauth.anyRequest().authenticated()catch-all should contain the blast radius even on 4.0.0, but upgrading to 4.0.6 where Boot's own Actuator auto-configuration is fixed is still the right call.application.yamlmanagement:block only configureshealth.mail.enabled: false— nomanagement.endpoints.web.exposure.includeis set. Boot 4 defaults to exposing onlyhealth, which is safe. However, this default is implicit; it should be made explicit inapplication.yamlso it cannot be silently changed by a Boot upgrade.20240325.1→20260101.1) is independent of the Boot BOM and correctly tracked as a separate explicit version pin.docker-compose.ymlusesminio/minio:latestandaxllent/mailpit:latest— both are unpinned image tags. This is pre-existing tech debt, separate from this issue, but worth noting before production.docs/architecture/diagrams are unaffected. This issue correctly scopes to one file.Recommendations
management.endpoints.web.exposure.includekey explicitly toapplication.yamlin the same PR. The default is safe, but the safe default is invisible. Make itinclude: healthso it is documented, intentional, and will not silently expand if Boot's auto-configuration defaults change in a future minor.minio/minioandaxllent/mailpitimage tags indocker-compose.ymlas a follow-up P2 issue — not a blocker for this PR, but:latesttags mean the local dev environment is non-reproducible and could drift in a way that masks or introduces bugs. Renovate is already configured; adding pin rules there is the path of least resistance../mvnw clean verify(not just./mvnw test) in the verification step — JaCoCo's coverage gate (minimum: 0.88branch) only executes in theverifyphase. The issue already lists this; just making it explicit thattestalone does not validate the coverage gate.👨💻 Felix Brandt — Fullstack Developer
Observations
spring-boot-starter-parentat4.0.0,owasp-java-html-sanitizerat20240325.1. Both need updating exactly as described.<version>managed by the Boot BOM — the explicit<version>20240325.1</version>in pom.xml is the only pin. The bump to20260101.1must be applied manually; it will not come from the Boot parent.5.5.0) and AWS SDK v2 (2.29.0) have explicit version overrides outside the BOM. Those should be left as-is unless the implementer separately confirms they are safe.npm run generate:api && npm run check. However, the backend must be running with--spring.profiles.active=devforgenerate:apito work (the dev profile enables the OpenAPI spec endpoint). The Swagger UI and api-docs are disabled in production profile perapplication.yaml.0.88). A Boot version bump should not reduce coverage since no production code changes — but the./mvnw clean verifyrun is still required to confirm.Recommendations
spring-boot-starter-parent→4.0.6,owasp-java-html-sanitizer→20260101.1. Do not split into two commits — they are both dependency pins, and splitting creates a window where the OWASP CVE is still open.poi(5.5.0) oraws-sdk(2.29.0) in this PR. They are outside the Boot BOM scope, and mixing unrelated bumps risks introducing unreviewed breaking changes. Separate PRs for those.cd frontend && npm run generate:api && npm run check. A Boot version bump can theoretically affect the serialization of a response shape (e.g., a new Jackson default), and a type drift will show up here before it shows up in production.security(deps): bump Spring Boot 4.0.0 → 4.0.6 and OWASP sanitizer 20240325.1 → 20260101.1— consistent with the existingsecurity(deps):prefix in the issue title and the project's commit convention.🛠️ Tobias Wendt — DevOps & Platform Engineer
Observations
pom.xmlchange — no new Docker service, no Compose overlay, no CI workflow modification required. The existing CI pipeline (ci.yml) will pick up the new version automatically on the next push.backend-unit-testsjob runs./mvnw clean test— this misses the JaCoCo coverage gate, which only fires on./mvnw clean verify. For a security-only bump this is low-risk, but the issue's verification step 2 correctly specifiesclean verify. The CI job should ideally also runverifyrather than justtest, but that is a pre-existing gap, not introduced by this PR.docker-compose.ymlhas two:latestimage tags:minio/minio:latestandaxllent/mailpit:latest. These are dev-only services and the compose file is not used in production, so they do not block this PR. But Renovate is configured (renovate.jsonexists) and can be extended to pin these.schedule, noautomerge: truefor patches, and no platform configuration for Gitea. This means Renovate may not have been running dependency update PRs automatically, which is the likely root cause of why Boot 4.0.0 went unpatched to 4.0.6 across six patch releases.trivy fsrun from the audit. There is no automated CVE gate in the pipeline that would have caught this proactively or would validate its resolution automatically.docker compose up -d, hit three endpoints). No automated post-deploy smoke test exists in CI.Recommendations
"platform": "gitea","gitea": { "endpoint": "http://heim-nas:3005" }, and"automerge": truefor patch updates. This prevents the same "six patch releases behind" situation from recurring for Spring Boot, Jackson, PostgreSQL driver, and other BOM-managed transitive dependencies.trivy fsstep to the CI pipeline as a follow-up issue. Minimum viable form::latestMinIO and Mailpit tags indocker-compose.ymlin a follow-up issue — not a blocker here, but Renovate can manage them once pinned.Open Decisions
backend-unit-testsjob, or run as a separate parallel job? Parallel is better for speed; sequential gives a clear "CVE → build failed" signal in one log.📋 Elicit — Requirements Engineer
Observations
trivyoutput is 0 CRITICAL ✓, test suite passes ✓, Docker stack healthy ✓, type check clean ✓. No vague adjectives.20260101.1. The issue body mentions it in "Critical files" but it does not appear in the acceptance criteria checklist. A reviewer closing this issue could mark it done while forgetting the sanitizer bump.Recommendations
- [ ] OWASP Java HTML Sanitizer is pinned to 20260101.1 in pom.xml.— mirrors the Spring Boot criterion and prevents the sanitizer bump from being accidentally omitted.🔐 Nora "NullX" Steiner — Security Engineer
Observations
CVE-2026-40976 — Actuator pre-auth bypass (CRIT): The existing
SecurityConfig.javahasauth.anyRequest().authenticated()as the catch-all, plus an explicitpermitAll()only for/actuator/health. This is correctly locked down at the application layer. The Boot 4.0.0 bug is in how Boot's own auto-configuration sets up the Actuator security filter chain. Even with the correctSecurityConfig, Boot 4.0.0 may insert a secondary filter chain ahead of the application's chain that bypasses theanyRequest().authenticated()rule. Upgrading to 4.0.6 fixes Boot's auto-configuration so no secondary chain is inserted. Both the code-level control and the framework fix are needed.CVE-2026-22732 — Spring Security Web pre-auth bypass (CRIT): Spring Security 7.0.0 is pulled as a transitive dependency of
spring-boot-starter-security. The fix lands in 7.0.4 (via Boot 4.0.6's BOM). No application code change is required — the framework upgrade carries it.Actuator exposure configuration:
management.endpoints.web.exposure.includeis not explicitly configured inapplication.yaml. Boot 4's default exposes onlyhealth. This is correct, but the default is implicit. An explicitmanagement.endpoints.web.exposure.include: healthin production config is defense-in-depth documentation — it ensures a future Boot upgrade that changes the default cannot silently re-expose endpoints.OWASP sanitizer (CVE-2025-66021): The current version
20240325.1is vulnerable. The Geschichten domain uses server-side HTML sanitization (GeschichteServicevia this library) as defense-in-depth alongside client-side Tiptap. Leaving this unpatched means the server-side sanitization layer has a known bypass, reducing it from defense-in-depth to single-layer protection.Threat model confirmation: The
SecurityConfig.javaCSRF comment is well-written and technically accurate — Basic Auth + custom Authorization header + SameSite cookie policy makes CSRF structurally impossible under the current auth model. No change needed here.No Trivy gate in CI: The audit found these CVEs via manual
trivy fsrun. Without an automated gate, CVEs accumulate silently between manual audits. This is the root cause of being six patch releases behind.Recommendations
trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xmllocally before pushing. Do not rely solely on the test suite — it validates behavior, not vulnerability presence. Trivy must report 0 CRITICAL after the bump.management.endpoints.web.exposure.include: healthexplicitly toapplication.yamlin the same PR. This converts an implicit safe default into an explicit, auditable security configuration. Takes 30 seconds.trivyto CI as a CRITICAL-severity gate. A 30-minute fix was needed because a manual audit caught what automation should have caught months earlier.Open Decisions
ApplicationContextTest.java(which already does a full-context smoke test) or in a dedicatedActuatorSecurityTest.java? A dedicated file is searchable and makes the security intent explicit; the existing file avoids a new test class for two tests.🧪 Sara Holt — QA Engineer
Observations
pom.xml— no application logic, no schema migration, no new routes. The existing test suite is the primary verification vehicle.0.88branch coverage minimum, gated in theverifyphase. The CI job runs./mvnw clean test, not./mvnw clean verify— so the coverage gate does not run in CI for this project. This is a pre-existing gap. The issue's manual verification step correctly says./mvnw clean verify, but CI does not enforce it automatically.SecurityUtilsTest.javaexists but there is noActuatorSecurityTestor similar class that explicitly verifies which Actuator endpoints are accessible unauthenticated. The Actuator auth restriction lives only inSecurityConfig.javawith no regression test anchoring it. CVE-2026-40976 is exactly the class of bug that an Actuator auth boundary test would catch.DOCKER_API_VERSION: "1.43"env var in CI suggests there are runner-specific container constraints).SseEmitterRegistryTestandOcrBatchServiceasync tests are the most likely candidates. If the suite fails after the bump, check those before assuming a CVE patch itself is the cause.Recommendations
test→verifygap inci.ymlin the same PR. Change./mvnw clean testto./mvnw clean verifyin thebackend-unit-testsjob. The JaCoCo gate should run on every push, not only in manual local runs. This is a one-word change.GET /actuator/healthreturns 200 without auth, andGET /actuator/envreturns 401. These become permanent fixtures that prevent CVE-2026-40976-class regressions. Place them in a dedicatedActuatorSecurityTest.javarather thanApplicationContextTest.javaso the security intent is findable.DOCKER_API_VERSIONenv var may need adjustment.Open Decisions
./mvnw clean testto./mvnw clean verify? The coverage gate adds ~10-15 seconds to CI time. Given that the gate threshold is already in the POM as a first-class requirement, running it in CI seems correct — but this touches CI scope beyond this issue's stated S effort estimate.🎨 Leonie Voss — UI/UX & Accessibility
Observations
This is a pure backend dependency security update — no UI, no component, no route, no design token, no i18n string is touched. From a UI/UX and accessibility standpoint, this change is a no-op in terms of rendered output.
There is exactly one user-facing surface relevant to this issue: error messages surfaced when a security-related backend error occurs. If CVE-2026-22732 (Spring Security policy bypass) previously allowed unauthenticated access to return data that should have been blocked, patching it could cause some requests that previously succeeded to now return 401. Users seeing a sudden 401 will hit the error-handling path in the frontend.
I verified that the frontend uses
getErrorMessage(code)via the centralized error mapping — raw backend error bodies are not shown to users. The Paraglide i18n system handles the 401 error state. No new error codes are introduced by this patch. Users who encounter a newly-enforced auth boundary will see the existing "unauthorized" i18n message, which is appropriate.Recommendations
!result.response.ok→getErrorMessage) is correct and will gracefully handle any 401 responses introduced by the security fix.🗳️ Decision Queue — Open Decisions from Review
Three open decisions were raised across personas. They are grouped by theme.
Theme 1: CI Pipeline scope for this PR
Tobias and Sara both identified the same pre-existing CI gap: the
backend-unit-testsjob runs./mvnw clean testinstead of./mvnw clean verify, which means the JaCoCo 88% branch coverage gate never fires in CI. Sara also raised it as an Open Decision:Recommendation from review team: Fix it in this PR (one-word change, same commit). The gap is a pre-existing defect, and this PR is the natural insertion point. If the effort estimate is a concern, the S rating already included
./mvnw clean verifyin the verification checklist — the CI fix just makes it automatic.Theme 2: Trivy in CI — parallel or sequential?
Tobias raised:
This decision belongs in the follow-up Trivy CI issue rather than this PR. No action needed here.
Theme 3: Actuator regression test placement
Nora raised, and Sara echoed:
Recommendation from review team: Create a dedicated
ActuatorSecurityTest.java. Two reasons: (1) the security intent is immediately findable by name when auditing the test directory; (2)ApplicationContextTest.javais a smoke test, not a security boundary test — conflating them blurs the purpose of each class. The cost is one extra file.