security(deps): bump Spring Boot to 4.0.6 to clear 2 CRIT + 17 HIGH CVEs #457

Open
opened 2026-05-07 17:21:13 +02:00 by marcel · 8 comments
Owner

Context

Pre-prod audit (docs/audits/2026-05-07-pre-prod-architectural-review.md Appendix A.1.1) ran trivy fs against backend/pom.xml and 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:

  • CVE-2026-40976 (CRIT) — spring-boot@4.0.0: default security filter chain has no authorization rule with Actuator. Fixed in 4.0.6.
  • CVE-2026-22732 (CRIT) — 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 via trivy fs and the existing test suite.

Critical files

  • backend/pom.xml<parent><artifactId>spring-boot-starter-parent</artifactId><version>4.0.0</version>4.0.6
  • backend/pom.xml — also bump <dependency>com.googlecode.owasp-java-html-sanitizer</dependency> to 20260101.1 (CVE-2025-66021, not in the Boot BOM)

Verification

  1. CVE check: trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xml should report 0 CRITICAL and ≤2 HIGH (whatever isn't yet patched upstream).
  2. Tests: ./mvnw clean verify passes with 88% JaCoCo branch-coverage gate intact.
  3. App smoke: docker compose up -d, login as admin, hit /api/users/me, /api/documents, /api/documents/search?q=walter — all 200.
  4. Integration: ArchUnit suite passes (./mvnw test -Dtest='*ArchTest').
  5. Confirm no API regressions by regenerating frontend types: cd frontend && npm run generate:api && npm run check.

Acceptance criteria

  • Spring Boot version is 4.0.6 (or later if released by merge time).
  • OWASP Java HTML Sanitizer is 20260101.1.
  • trivy fs backend/pom.xml reports 0 CRITICAL.
  • All existing tests pass; coverage gate not regressed.
  • docker compose up -d healthy across all services.
  • Frontend npm run generate:api && npm run check clean (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).

## Context Pre-prod audit (`docs/audits/2026-05-07-pre-prod-architectural-review.md` Appendix A.1.1) ran `trivy fs` against `backend/pom.xml` and 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: - **CVE-2026-40976** (CRIT) — `spring-boot@4.0.0`: default security filter chain has no authorization rule with Actuator. Fixed in 4.0.6. - **CVE-2026-22732** (CRIT) — `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 via `trivy fs` and the existing test suite. ## Critical files - `backend/pom.xml` — `<parent><artifactId>spring-boot-starter-parent</artifactId><version>4.0.0</version>` → `4.0.6` - `backend/pom.xml` — also bump `<dependency>com.googlecode.owasp-java-html-sanitizer</dependency>` to `20260101.1` (CVE-2025-66021, not in the Boot BOM) ## Verification 1. **CVE check**: `trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xml` should report 0 CRITICAL and ≤2 HIGH (whatever isn't yet patched upstream). 2. **Tests**: `./mvnw clean verify` passes with 88% JaCoCo branch-coverage gate intact. 3. **App smoke**: `docker compose up -d`, login as admin, hit `/api/users/me`, `/api/documents`, `/api/documents/search?q=walter` — all 200. 4. **Integration**: ArchUnit suite passes (`./mvnw test -Dtest='*ArchTest'`). 5. Confirm no API regressions by regenerating frontend types: `cd frontend && npm run generate:api && npm run check`. ## Acceptance criteria - [ ] Spring Boot version is `4.0.6` (or later if released by merge time). - [ ] OWASP Java HTML Sanitizer is `20260101.1`. - [ ] `trivy fs backend/pom.xml` reports 0 CRITICAL. - [ ] All existing tests pass; coverage gate not regressed. - [ ] `docker compose up -d` healthy across all services. - [ ] Frontend `npm run generate:api && npm run check` clean (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).
marcel added this to the Production v1 milestone 2026-05-07 17:21:13 +02:00
marcel added the P0-criticaldevopsphase-1: securitysecurity labels 2026-05-07 17:23:29 +02:00
Author
Owner

🏗️ Markus Keller — Application Architect

Observations

  • The change is a single-line version bump in the parent POM — no module boundary changes, no new Spring context loading, no transport protocol shifts. Structurally clean.
  • SecurityConfig.java already restricts Actuator correctly: /actuator/health is the only endpoint exposed without auth. The issue body references CVE-2026-40976 (Actuator auth bypass in the default filter chain) — the existing auth.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.
  • The application.yaml management: block only configures health.mail.enabled: false — no management.endpoints.web.exposure.include is set. Boot 4 defaults to exposing only health, which is safe. However, this default is implicit; it should be made explicit in application.yaml so it cannot be silently changed by a Boot upgrade.
  • The OWASP sanitizer bump (20240325.120260101.1) is independent of the Boot BOM and correctly tracked as a separate explicit version pin.
  • The docker-compose.yml uses minio/minio:latest and axllent/mailpit:latest — both are unpinned image tags. This is pre-existing tech debt, separate from this issue, but worth noting before production.
  • No architectural documentation requires updating: no new package, no new route, no new infrastructure component, no new entity, no new transport. The docs/architecture/ diagrams are unaffected. This issue correctly scopes to one file.

Recommendations

  1. Add the management.endpoints.web.exposure.include key explicitly to application.yaml in the same PR. The default is safe, but the safe default is invisible. Make it include: health so it is documented, intentional, and will not silently expand if Boot's auto-configuration defaults change in a future minor.
  2. Pin minio/minio and axllent/mailpit image tags in docker-compose.yml as a follow-up P2 issue — not a blocker for this PR, but :latest tags 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.
  3. Run ./mvnw clean verify (not just ./mvnw test) in the verification step — JaCoCo's coverage gate (minimum: 0.88 branch) only executes in the verify phase. The issue already lists this; just making it explicit that test alone does not validate the coverage gate.
## 🏗️ Markus Keller — Application Architect ### Observations - The change is a single-line version bump in the parent POM — no module boundary changes, no new Spring context loading, no transport protocol shifts. Structurally clean. - `SecurityConfig.java` already restricts Actuator correctly: `/actuator/health` is the only endpoint exposed without auth. The issue body references CVE-2026-40976 (Actuator auth bypass in the default filter chain) — the existing `auth.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. - The `application.yaml` `management:` block only configures `health.mail.enabled: false` — no `management.endpoints.web.exposure.include` is set. Boot 4 defaults to exposing only `health`, which is safe. However, this default is implicit; it should be made explicit in `application.yaml` so it cannot be silently changed by a Boot upgrade. - The OWASP sanitizer bump (`20240325.1` → `20260101.1`) is independent of the Boot BOM and correctly tracked as a separate explicit version pin. - The `docker-compose.yml` uses `minio/minio:latest` and `axllent/mailpit:latest` — both are unpinned image tags. This is pre-existing tech debt, separate from this issue, but worth noting before production. - No architectural documentation requires updating: no new package, no new route, no new infrastructure component, no new entity, no new transport. The `docs/architecture/` diagrams are unaffected. This issue correctly scopes to one file. ### Recommendations 1. **Add the `management.endpoints.web.exposure.include` key explicitly to `application.yaml` in the same PR.** The default is safe, but the safe default is invisible. Make it `include: health` so it is documented, intentional, and will not silently expand if Boot's auto-configuration defaults change in a future minor. 2. **Pin `minio/minio` and `axllent/mailpit` image tags in `docker-compose.yml` as a follow-up P2 issue** — not a blocker for this PR, but `:latest` tags 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. 3. **Run `./mvnw clean verify` (not just `./mvnw test`)** in the verification step — JaCoCo's coverage gate (`minimum: 0.88` branch) only executes in the `verify` phase. The issue already lists this; just making it explicit that `test` alone does not validate the coverage gate.
Author
Owner

👨‍💻 Felix Brandt — Fullstack Developer

Observations

  • This is a pure dependency version bump — no Java, TypeScript, or Svelte code changes in scope. My review focuses on implementation correctness, test discipline, and the verification checklist the implementer needs to execute.
  • pom.xml currently: spring-boot-starter-parent at 4.0.0, owasp-java-html-sanitizer at 20240325.1. Both need updating exactly as described.
  • OWASP sanitizer has no <version> managed by the Boot BOM — the explicit <version>20240325.1</version> in pom.xml is the only pin. The bump to 20260101.1 must be applied manually; it will not come from the Boot parent.
  • The Apache POI dependency (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.
  • The issue's verification step 5 correctly calls npm run generate:api && npm run check. However, the backend must be running with --spring.profiles.active=dev for generate:api to work (the dev profile enables the OpenAPI spec endpoint). The Swagger UI and api-docs are disabled in production profile per application.yaml.
  • The JaCoCo coverage gate is at 88% branch minimum (pom.xml confirms 0.88). A Boot version bump should not reduce coverage since no production code changes — but the ./mvnw clean verify run is still required to confirm.
  • TDD note: this issue has no application logic changes, so there is no failing test to write first. The verification is: run the existing suite and confirm it stays green.

Recommendations

  1. Make the two changes atomically in one commit: spring-boot-starter-parent4.0.6, owasp-java-html-sanitizer20260101.1. Do not split into two commits — they are both dependency pins, and splitting creates a window where the OWASP CVE is still open.
  2. Do not bump poi (5.5.0) or aws-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.
  3. Verify the type generation step with the backend running before pushing: 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.
  4. Commit message format: security(deps): bump Spring Boot 4.0.0 → 4.0.6 and OWASP sanitizer 20240325.1 → 20260101.1 — consistent with the existing security(deps): prefix in the issue title and the project's commit convention.
## 👨‍💻 Felix Brandt — Fullstack Developer ### Observations - This is a pure dependency version bump — no Java, TypeScript, or Svelte code changes in scope. My review focuses on implementation correctness, test discipline, and the verification checklist the implementer needs to execute. - **pom.xml currently:** `spring-boot-starter-parent` at `4.0.0`, `owasp-java-html-sanitizer` at `20240325.1`. Both need updating exactly as described. - **OWASP sanitizer has no `<version>` managed by the Boot BOM** — the explicit `<version>20240325.1</version>` in pom.xml is the only pin. The bump to `20260101.1` must be applied manually; it will not come from the Boot parent. - The Apache POI dependency (`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. - The issue's verification step 5 correctly calls `npm run generate:api && npm run check`. However, the backend must be running with `--spring.profiles.active=dev` for `generate:api` to work (the dev profile enables the OpenAPI spec endpoint). The Swagger UI and api-docs are disabled in production profile per `application.yaml`. - The JaCoCo coverage gate is at 88% branch minimum (pom.xml confirms `0.88`). A Boot version bump should not reduce coverage since no production code changes — but the `./mvnw clean verify` run is still required to confirm. - TDD note: this issue has no application logic changes, so there is no failing test to write first. The verification is: run the existing suite and confirm it stays green. ### Recommendations 1. **Make the two changes atomically in one commit:** `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. 2. **Do not bump `poi` (5.5.0) or `aws-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. 3. **Verify the type generation step with the backend running before pushing:** `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. 4. **Commit message format:** `security(deps): bump Spring Boot 4.0.0 → 4.0.6 and OWASP sanitizer 20240325.1 → 20260101.1` — consistent with the existing `security(deps):` prefix in the issue title and the project's commit convention.
Author
Owner

🛠️ Tobias Wendt — DevOps & Platform Engineer

Observations

  • The Boot bump is a straightforward pom.xml change — 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.
  • CI coverage of this change: the backend-unit-tests job 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 specifies clean verify. The CI job should ideally also run verify rather than just test, but that is a pre-existing gap, not introduced by this PR.
  • docker-compose.yml has two :latest image tags: minio/minio:latest and axllent/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.json exists) and can be extended to pin these.
  • The Renovate config is minimal — it only groups TipTap updates and disables automerge for them. It has no schedule, no automerge: true for 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.
  • No Trivy scan step in CI. The issue identifies the vulnerability via a manual trivy fs run from the audit. There is no automated CVE gate in the pipeline that would have caught this proactively or would validate its resolution automatically.
  • Smoke test verification: the issue's verification step 3 is a manual check (docker compose up -d, hit three endpoints). No automated post-deploy smoke test exists in CI.

Recommendations

  1. Fix the Renovate config to enable Gitea platform and patch automerge. Add "platform": "gitea", "gitea": { "endpoint": "http://heim-nas:3005" }, and "automerge": true for 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.
  2. Add a trivy fs step to the CI pipeline as a follow-up issue. Minimum viable form:
    - name: Scan for CRITICAL CVEs
      uses: aquasecurity/trivy-action@v0.30.0
      with:
        scan-type: fs
        scan-ref: backend/pom.xml
        severity: CRITICAL
        exit-code: 1
    
    This would have caught CVE-2026-40976 and CVE-2026-22732 automatically on any push that introduced them.
  3. Pin the :latest MinIO and Mailpit tags in docker-compose.yml in a follow-up issue — not a blocker here, but Renovate can manage them once pinned.

Open Decisions

  • Should the Trivy CI step block the backend-unit-tests job, or run as a separate parallel job? Parallel is better for speed; sequential gives a clear "CVE → build failed" signal in one log.
## 🛠️ Tobias Wendt — DevOps & Platform Engineer ### Observations - The Boot bump is a straightforward `pom.xml` change — 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. - **CI coverage of this change:** the `backend-unit-tests` job 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 specifies `clean verify`. The CI job should ideally also run `verify` rather than just `test`, but that is a pre-existing gap, not introduced by this PR. - **`docker-compose.yml` has two `:latest` image tags:** `minio/minio:latest` and `axllent/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.json` exists) and can be extended to pin these. - **The Renovate config is minimal** — it only groups TipTap updates and disables automerge for them. It has no `schedule`, no `automerge: true` for 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. - **No Trivy scan step in CI.** The issue identifies the vulnerability via a manual `trivy fs` run from the audit. There is no automated CVE gate in the pipeline that would have caught this proactively or would validate its resolution automatically. - **Smoke test verification:** the issue's verification step 3 is a manual check (`docker compose up -d`, hit three endpoints). No automated post-deploy smoke test exists in CI. ### Recommendations 1. **Fix the Renovate config to enable Gitea platform and patch automerge.** Add `"platform": "gitea"`, `"gitea": { "endpoint": "http://heim-nas:3005" }`, and `"automerge": true` for 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. 2. **Add a `trivy fs` step to the CI pipeline** as a follow-up issue. Minimum viable form: ```yaml - name: Scan for CRITICAL CVEs uses: aquasecurity/trivy-action@v0.30.0 with: scan-type: fs scan-ref: backend/pom.xml severity: CRITICAL exit-code: 1 ``` This would have caught CVE-2026-40976 and CVE-2026-22732 automatically on any push that introduced them. 3. **Pin the `:latest` MinIO and Mailpit tags in `docker-compose.yml` in a follow-up issue** — not a blocker here, but Renovate can manage them once pinned. ### Open Decisions - Should the Trivy CI step block the `backend-unit-tests` job, or run as a separate parallel job? Parallel is better for speed; sequential gives a clear "CVE → build failed" signal in one log.
Author
Owner

📋 Elicit — Requirements Engineer

Observations

  • The issue is extremely well-specified for a dependency update: clear context (audit reference, CVE IDs, affected components), a precise approach (single-line bump), named critical files, a numbered verification checklist, binary acceptance criteria, effort estimate, and a risk statement. This meets the Definition of Ready without modification.
  • Every acceptance criterion is testable and binary: version number visible in pom.xml ✓, trivy output is 0 CRITICAL ✓, test suite passes ✓, Docker stack healthy ✓, type check clean ✓. No vague adjectives.
  • The cross-reference to #87 (Actuator restriction) is valuable — it locates this issue in a chain of security hardening. The requirement engineer perspective: #87 and #457 together address the Actuator attack surface; neither alone is sufficient.
  • Risk statement is honest and specific: pre-auth bypass + request smuggling + DoS are named with CVE IDs. This allows a non-technical stakeholder to understand why S-sized work is P0-critical.
  • One gap in the acceptance criteria: there is no criterion for the OWASP sanitizer being at 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

  1. Add a sixth acceptance criterion: - [ ] 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.
  2. The effort estimate (S, 30 minutes) is accurate — this is a single-line change with a well-defined verification checklist. No re-estimation needed.
  3. Consider tagging this as a dependency on #87 in Gitea's "Related issues" field — the two issues together constitute the Actuator hardening chain, and the relationship is currently only visible in the prose body. A formal link improves traceability for anyone triaging the milestone.
## 📋 Elicit — Requirements Engineer ### Observations - The issue is extremely well-specified for a dependency update: clear context (audit reference, CVE IDs, affected components), a precise approach (single-line bump), named critical files, a numbered verification checklist, binary acceptance criteria, effort estimate, and a risk statement. This meets the Definition of Ready without modification. - **Every acceptance criterion is testable and binary:** version number visible in pom.xml ✓, `trivy` output is 0 CRITICAL ✓, test suite passes ✓, Docker stack healthy ✓, type check clean ✓. No vague adjectives. - **The cross-reference to #87** (Actuator restriction) is valuable — it locates this issue in a chain of security hardening. The requirement engineer perspective: #87 and #457 together address the Actuator attack surface; neither alone is sufficient. - **Risk statement is honest and specific:** pre-auth bypass + request smuggling + DoS are named with CVE IDs. This allows a non-technical stakeholder to understand why S-sized work is P0-critical. - **One gap in the acceptance criteria:** there is no criterion for the OWASP sanitizer being at `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 1. **Add a sixth acceptance criterion:** `- [ ] 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. 2. **The effort estimate (S, 30 minutes) is accurate** — this is a single-line change with a well-defined verification checklist. No re-estimation needed. 3. **Consider tagging this as a dependency on #87** in Gitea's "Related issues" field — the two issues together constitute the Actuator hardening chain, and the relationship is currently only visible in the prose body. A formal link improves traceability for anyone triaging the milestone.
Author
Owner

🔐 Nora "NullX" Steiner — Security Engineer

Observations

CVE-2026-40976 — Actuator pre-auth bypass (CRIT): The existing SecurityConfig.java has auth.anyRequest().authenticated() as the catch-all, plus an explicit permitAll() 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 correct SecurityConfig, Boot 4.0.0 may insert a secondary filter chain ahead of the application's chain that bypasses the anyRequest().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.include is not explicitly configured in application.yaml. Boot 4's default exposes only health. This is correct, but the default is implicit. An explicit management.endpoints.web.exposure.include: health in 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.1 is vulnerable. The Geschichten domain uses server-side HTML sanitization (GeschichteService via 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.java CSRF 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 fs run. Without an automated gate, CVEs accumulate silently between manual audits. This is the root cause of being six patch releases behind.

Recommendations

  1. Apply both bumps together and run trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xml locally before pushing. Do not rely solely on the test suite — it validates behavior, not vulnerability presence. Trivy must report 0 CRITICAL after the bump.
  2. Add management.endpoints.web.exposure.include: health explicitly to application.yaml in the same PR. This converts an implicit safe default into an explicit, auditable security configuration. Takes 30 seconds.
  3. Add a regression test for the Actuator auth boundary in the same PR — not as a gate, but as a permanent fixture:
    @Test
    void actuator_health_is_accessible_without_auth() {
        mockMvc.perform(get("/actuator/health"))
            .andExpect(status().isOk());
    }
    
    @Test
    void actuator_env_requires_authentication() {
        mockMvc.perform(get("/actuator/env"))
            .andExpect(status().isUnauthorized());
    }
    
    These tests prove that Boot's Actuator auto-configuration is correctly restricted. They catch any future regression — including the class of bug that CVE-2026-40976 represents — without waiting for the next audit.
  4. File a separate P1 issue to add trivy to 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

  • Should the Actuator security regression tests live in ApplicationContextTest.java (which already does a full-context smoke test) or in a dedicated ActuatorSecurityTest.java? A dedicated file is searchable and makes the security intent explicit; the existing file avoids a new test class for two tests.
## 🔐 Nora "NullX" Steiner — Security Engineer ### Observations **CVE-2026-40976 — Actuator pre-auth bypass (CRIT):** The existing `SecurityConfig.java` has `auth.anyRequest().authenticated()` as the catch-all, plus an explicit `permitAll()` 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 correct `SecurityConfig`, Boot 4.0.0 may insert a secondary filter chain ahead of the application's chain that bypasses the `anyRequest().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.include` is not explicitly configured in `application.yaml`. Boot 4's default exposes only `health`. This is correct, but the default is implicit. An explicit `management.endpoints.web.exposure.include: health` in 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.1` is vulnerable. The Geschichten domain uses server-side HTML sanitization (`GeschichteService` via 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.java` CSRF 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 fs` run. Without an automated gate, CVEs accumulate silently between manual audits. This is the root cause of being six patch releases behind. ### Recommendations 1. **Apply both bumps together and run `trivy fs --scanners vuln --severity HIGH,CRITICAL backend/pom.xml` locally before pushing.** Do not rely solely on the test suite — it validates behavior, not vulnerability presence. Trivy must report 0 CRITICAL after the bump. 2. **Add `management.endpoints.web.exposure.include: health` explicitly to `application.yaml`** in the same PR. This converts an implicit safe default into an explicit, auditable security configuration. Takes 30 seconds. 3. **Add a regression test for the Actuator auth boundary** in the same PR — not as a gate, but as a permanent fixture: ```java @Test void actuator_health_is_accessible_without_auth() { mockMvc.perform(get("/actuator/health")) .andExpect(status().isOk()); } @Test void actuator_env_requires_authentication() { mockMvc.perform(get("/actuator/env")) .andExpect(status().isUnauthorized()); } ``` These tests prove that Boot's Actuator auto-configuration is correctly restricted. They catch any future regression — including the class of bug that CVE-2026-40976 represents — without waiting for the next audit. 4. **File a separate P1 issue to add `trivy` to 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 - Should the Actuator security regression tests live in `ApplicationContextTest.java` (which already does a full-context smoke test) or in a dedicated `ActuatorSecurityTest.java`? A dedicated file is searchable and makes the security intent explicit; the existing file avoids a new test class for two tests.
Author
Owner

🧪 Sara Holt — QA Engineer

Observations

  • The change touches only pom.xml — no application logic, no schema migration, no new routes. The existing test suite is the primary verification vehicle.
  • Existing coverage gate: JaCoCo is configured at 0.88 branch coverage minimum, gated in the verify phase. 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.
  • Security boundary tests: I checked the test directory. SecurityUtilsTest.java exists but there is no ActuatorSecurityTest or similar class that explicitly verifies which Actuator endpoints are accessible unauthenticated. The Actuator auth restriction lives only in SecurityConfig.java with no regression test anchoring it. CVE-2026-40976 is exactly the class of bug that an Actuator auth boundary test would catch.
  • Test pyramid impact of this change: zero. No new unit tests, integration tests, or E2E tests are needed for the version bump itself. The verification is behavioral: does the existing suite stay green?
  • Testcontainers version: managed by the Boot BOM — bumping Boot to 4.0.6 will also bump Testcontainers to whatever 4.0.6 declares. This is safe, but worth confirming no container startup behavior changes (the DOCKER_API_VERSION: "1.43" env var in CI suggests there are runner-specific container constraints).
  • Flaky test risk: Boot version bumps occasionally surface flaky async tests when framework threading or timeout defaults change. The SseEmitterRegistryTest and OcrBatchService async 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

  1. Fix the CI testverify gap in ci.yml in the same PR. Change ./mvnw clean test to ./mvnw clean verify in the backend-unit-tests job. The JaCoCo gate should run on every push, not only in manual local runs. This is a one-word change.
  2. Add two Actuator auth regression tests (as Nora also recommends) — GET /actuator/health returns 200 without auth, and GET /actuator/env returns 401. These become permanent fixtures that prevent CVE-2026-40976-class regressions. Place them in a dedicated ActuatorSecurityTest.java rather than ApplicationContextTest.java so the security intent is findable.
  3. After the bump, run the integration tests and check for container startup failures caused by the Testcontainers version change dragged in by Boot 4.0.6. If the NAS CI runner hits a Docker API version issue with the new Testcontainers, the DOCKER_API_VERSION env var may need adjustment.

Open Decisions

  • Should the CI backend job permanently switch from ./mvnw clean test to ./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.
## 🧪 Sara Holt — QA Engineer ### Observations - The change touches only `pom.xml` — no application logic, no schema migration, no new routes. The existing test suite is the primary verification vehicle. - **Existing coverage gate:** JaCoCo is configured at `0.88` branch coverage minimum, gated in the `verify` phase. 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. - **Security boundary tests:** I checked the test directory. `SecurityUtilsTest.java` exists but there is no `ActuatorSecurityTest` or similar class that explicitly verifies which Actuator endpoints are accessible unauthenticated. The Actuator auth restriction lives only in `SecurityConfig.java` with no regression test anchoring it. CVE-2026-40976 is exactly the class of bug that an Actuator auth boundary test would catch. - **Test pyramid impact of this change:** zero. No new unit tests, integration tests, or E2E tests are needed for the version bump itself. The verification is behavioral: does the existing suite stay green? - **Testcontainers version:** managed by the Boot BOM — bumping Boot to 4.0.6 will also bump Testcontainers to whatever 4.0.6 declares. This is safe, but worth confirming no container startup behavior changes (the `DOCKER_API_VERSION: "1.43"` env var in CI suggests there are runner-specific container constraints). - **Flaky test risk:** Boot version bumps occasionally surface flaky async tests when framework threading or timeout defaults change. The `SseEmitterRegistryTest` and `OcrBatchService` async 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 1. **Fix the CI `test` → `verify` gap in `ci.yml` in the same PR.** Change `./mvnw clean test` to `./mvnw clean verify` in the `backend-unit-tests` job. The JaCoCo gate should run on every push, not only in manual local runs. This is a one-word change. 2. **Add two Actuator auth regression tests** (as Nora also recommends) — `GET /actuator/health` returns 200 without auth, and `GET /actuator/env` returns 401. These become permanent fixtures that prevent CVE-2026-40976-class regressions. Place them in a dedicated `ActuatorSecurityTest.java` rather than `ApplicationContextTest.java` so the security intent is findable. 3. **After the bump, run the integration tests and check for container startup failures** caused by the Testcontainers version change dragged in by Boot 4.0.6. If the NAS CI runner hits a Docker API version issue with the new Testcontainers, the `DOCKER_API_VERSION` env var may need adjustment. ### Open Decisions - Should the CI backend job permanently switch from `./mvnw clean test` to `./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.
Author
Owner

🎨 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

  1. No UI changes required. The existing error handling path (!result.response.okgetErrorMessage) is correct and will gracefully handle any 401 responses introduced by the security fix.
  2. No accessibility impact. No interactive elements, no color, no typography, no touch targets, no focus management changes.
  3. Smoke test the login flow after the upgrade — not for UX reasons, but because CVE-2026-22732 touches the Spring Security authentication path. If the patch changes any auth redirect behavior, the SvelteKit hooks that set the auth cookie should still work correctly. This is already in the issue's verification checklist (step 3: login as admin).
## 🎨 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 1. **No UI changes required.** The existing error handling path (`!result.response.ok` → `getErrorMessage`) is correct and will gracefully handle any 401 responses introduced by the security fix. 2. **No accessibility impact.** No interactive elements, no color, no typography, no touch targets, no focus management changes. 3. **Smoke test the login flow after the upgrade** — not for UX reasons, but because CVE-2026-22732 touches the Spring Security authentication path. If the patch changes any auth redirect behavior, the SvelteKit hooks that set the auth cookie should still work correctly. This is already in the issue's verification checklist (step 3: login as admin).
Author
Owner

🗳️ 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-tests job runs ./mvnw clean test instead of ./mvnw clean verify, which means the JaCoCo 88% branch coverage gate never fires in CI. Sara also raised it as an Open Decision:

Should the CI backend job permanently switch from ./mvnw clean test to ./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.

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 verify in the verification checklist — the CI fix just makes it automatic.


Theme 2: Trivy in CI — parallel or sequential?

Tobias raised:

Should the Trivy CI step block the backend-unit-tests job, or run as a separate parallel job? Parallel is better for speed; sequential gives a clear "CVE → build failed" signal in one log.

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:

Should the Actuator security regression tests live in ApplicationContextTest.java (which already does a full-context smoke test) or in a dedicated ActuatorSecurityTest.java?

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.java is a smoke test, not a security boundary test — conflating them blurs the purpose of each class. The cost is one extra file.

## 🗳️ 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-tests` job runs `./mvnw clean test` instead of `./mvnw clean verify`, which means the JaCoCo 88% branch coverage gate never fires in CI. Sara also raised it as an Open Decision: > Should the CI backend job permanently switch from `./mvnw clean test` to `./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. **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 verify` in the verification checklist — the CI fix just makes it automatic. --- ### Theme 2: Trivy in CI — parallel or sequential? **Tobias** raised: > Should the Trivy CI step block the `backend-unit-tests` job, or run as a separate parallel job? Parallel is better for speed; sequential gives a clear "CVE → build failed" signal in one log. 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: > Should the Actuator security regression tests live in `ApplicationContextTest.java` (which already does a full-context smoke test) or in a dedicated `ActuatorSecurityTest.java`? **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.java` is a smoke test, not a security boundary test — conflating them blurs the purpose of each class. The cost is one extra file.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#457