diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java
index 19eadf2b..5d2ea90b 100644
--- a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java
+++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java
@@ -1175,6 +1175,32 @@ class DocumentControllerTest {
.andExpect(jsonPath("$[0].pdfUrl").value("/api/documents/" + id + "/file"));
}
+ @Test
+ @WithMockUser(authorities = "WRITE_ALL")
+ void patchBulk_stripsCarriageReturnsAndNewlinesFromErrorMessages() throws Exception {
+ // Nora C4 — DocumentController.sanitizeForLog defends against
+ // CWE-117 (log injection) by replacing CR/LF in any free-form string
+ // it interpolates. Same helper now sanitises BulkEditError.message
+ // before it round-trips to the frontend.
+ when(userService.findByEmail(any())).thenReturn(AppUser.builder().id(UUID.randomUUID()).build());
+ UUID badId = UUID.randomUUID();
+ when(documentService.applyBulkEditToDocument(eq(badId), any(), any()))
+ .thenThrow(org.raddatz.familienarchiv.exception.DomainException.notFound(
+ org.raddatz.familienarchiv.exception.ErrorCode.DOCUMENT_NOT_FOUND,
+ "evil\r\nFAKE LOG ENTRY: admin logged in"));
+
+ mockMvc.perform(patch("/api/documents/bulk")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(bulkBody(badId.toString())))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.errors[0].message",
+ org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString("\n"))))
+ .andExpect(jsonPath("$.errors[0].message",
+ org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString("\r"))))
+ .andExpect(jsonPath("$.errors[0].message",
+ org.hamcrest.Matchers.containsString("evil_")));
+ }
+
@Test
@WithMockUser(authorities = "WRITE_ALL")
void patchBulk_returnsPartialFailureShape_whenServiceThrowsForOneDocument() throws Exception {
diff --git a/frontend/messages/de.json b/frontend/messages/de.json
index cd9e60d8..0384e776 100644
--- a/frontend/messages/de.json
+++ b/frontend/messages/de.json
@@ -896,5 +896,7 @@
"bulk_edit_clear_selection": "Auswahl aufheben",
"bulk_edit_clear_hint_keyboard": "Esc: Auswahl aufheben",
"bulk_edit_loading": "Dokumente werden geladen…",
- "bulk_edit_all_x_failed": "Filter konnte nicht abgerufen werden — bitte erneut versuchen."
+ "bulk_edit_all_x_failed": "Filter konnte nicht abgerufen werden — bitte erneut versuchen.",
+ "bulk_edit_topbar_title": "Massenbearbeitung",
+ "bulk_edit_count_pill": "{count} werden bearbeitet"
}
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 9c505a67..5e75ef71 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -896,5 +896,7 @@
"bulk_edit_clear_selection": "Clear selection",
"bulk_edit_clear_hint_keyboard": "Esc: clear selection",
"bulk_edit_loading": "Loading documents…",
- "bulk_edit_all_x_failed": "Could not load filter results — please retry."
+ "bulk_edit_all_x_failed": "Could not load filter results — please retry.",
+ "bulk_edit_topbar_title": "Bulk edit",
+ "bulk_edit_count_pill": "{count} will be edited"
}
diff --git a/frontend/messages/es.json b/frontend/messages/es.json
index fdd5dd22..12c2c5c9 100644
--- a/frontend/messages/es.json
+++ b/frontend/messages/es.json
@@ -896,5 +896,7 @@
"bulk_edit_clear_selection": "Limpiar selección",
"bulk_edit_clear_hint_keyboard": "Esc: limpiar selección",
"bulk_edit_loading": "Cargando documentos…",
- "bulk_edit_all_x_failed": "No se pudieron cargar los resultados del filtro; vuelve a intentarlo."
+ "bulk_edit_all_x_failed": "No se pudieron cargar los resultados del filtro; vuelve a intentarlo.",
+ "bulk_edit_topbar_title": "Edición masiva",
+ "bulk_edit_count_pill": "Se editarán {count}"
}
diff --git a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte
index 6dd18490..3e062058 100644
--- a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte
+++ b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte
@@ -324,12 +324,20 @@ async function retrySave() {
·
- {isMulti ? m.bulk_title_multi() : m.bulk_title_single()}
+ {#if mode === 'edit'}
+ {m.bulk_edit_topbar_title()}
+ {:else}
+ {isMulti ? m.bulk_title_multi() : m.bulk_title_single()}
+ {/if}
{#if isMulti}
- {m.bulk_count_pill({ count: files.size })}
+ {#if mode === 'edit'}
+ {m.bulk_edit_count_pill({ count: files.size })}
+ {:else}
+ {m.bulk_count_pill({ count: files.size })}
+ {/if}