From 92b0119a61470b721651cbd966b2ffc78896398b Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Tue, 21 Jul 2026 12:40:17 +0200 Subject: [PATCH 1/2] fix: use a finite default upload size when no scanner is present Without a malware scanner binding the default maximum upload size was Long.MAX_VALUE (effectively unlimited), so a missing scanner silently allowed unbounded attachment uploads and the associated resource exhaustion risk. The default is now a finite 400 MB regardless of the scanner binding (DEFAULT_MAX_UPLOAD_SIZE). Deployments that intentionally allow larger uploads can opt in via the new cds.attachments.maxUploadSize property, or override per entity with @Validation.Maximum. --- .../configuration/Registration.java | 28 +++++++++++++++---- .../ModifyApplicationHandlerHelper.java | 13 +++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java index 92c477234..884766d50 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java @@ -122,11 +122,9 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { // build malware scanner client, could be null if no service binding is available MalwareScanClient scanClient = buildMalwareScanClient(runtime.getEnvironment()); - // determine default max size based on malware scanner binding availability - String defaultMaxSize = - scanClient != null - ? ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER - : ModifyApplicationHandlerHelper.UNLIMITED_SIZE; + // determine default max size; independent of the scanner binding it stays finite so a missing + // scanner does not allow unbounded uploads. An explicit configuration can raise the limit. + String defaultMaxSize = resolveDefaultMaxSize(runtime.getEnvironment()); AttachmentMalwareScanner malwareScanner = new DefaultAttachmentMalwareScanner(persistenceService, attachmentService, scanClient); @@ -190,6 +188,26 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { } } + /** + * Resolves the default maximum upload size for attachment content. + * + *

An explicit {@code cds.attachments.maxUploadSize} configuration takes precedence. Otherwise + * a finite default is used regardless of the malware scanner binding, so that a missing scanner + * does not silently allow unbounded uploads. Consuming apps can still override the limit per + * entity via {@code @Validation.Maximum}. + * + * @param environment the {@link CdsEnvironment} to read configuration from + * @return the default maximum upload size as a size string (e.g. {@code "400MB"}) + */ + static String resolveDefaultMaxSize(CdsEnvironment environment) { + String configured = + environment.getProperty("cds.attachments.maxUploadSize", String.class, null); + if (configured != null && !configured.isBlank()) { + return configured; + } + return ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE; + } + /** * Builds the {@link MalwareScanClient malware scanner client} based on the service binding. * diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java index 2c315bbe9..ea7723286 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java @@ -23,11 +23,14 @@ public final class ModifyApplicationHandlerHelper { - /** Default max size when malware scanner binding is present (400 MB). */ - public static final String DEFAULT_SIZE_WITH_SCANNER = "400MB"; - - /** Effectively unlimited max size when no malware scanner binding is present. */ - public static final String UNLIMITED_SIZE = String.valueOf(Long.MAX_VALUE); + /** + * Default maximum upload size for attachment content (400 MB). A finite default is used + * regardless of the malware scanner binding so that a missing scanner does not silently allow + * unbounded uploads. It can be raised via the {@code cds.attachments.maxUploadSize} property or a + * per-entity {@code @Validation.Maximum} annotation, and matches the SAP Malware Scanning Service + * limit when a scanner is present. + */ + public static final String DEFAULT_MAX_UPLOAD_SIZE = "400MB"; /** * Handles attachments for entities. From c4a61935c548fbe5fa50e8007a64e07c909635e9 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Tue, 21 Jul 2026 12:41:42 +0200 Subject: [PATCH 2/2] test: cover finite default upload size resolution Adds RegistrationTest cases for resolveDefaultMaxSize (finite default when unconfigured or blank, configured override honored) and updates handler tests to the renamed DEFAULT_MAX_UPLOAD_SIZE constant. --- .../configuration/RegistrationTest.java | 30 +++++++++++++++++++ .../CreateAttachmentsHandlerTest.java | 2 +- .../UpdateAttachmentsHandlerTest.java | 2 +- .../ModifyApplicationHandlerHelperTest.java | 8 ++--- .../DraftPatchAttachmentsHandlerTest.java | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java index 3a4726a50..7edfecd9e 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java @@ -15,6 +15,7 @@ import com.sap.cds.feature.attachments.handler.applicationservice.DeleteAttachmentsHandler; import com.sap.cds.feature.attachments.handler.applicationservice.ReadAttachmentsHandler; import com.sap.cds.feature.attachments.handler.applicationservice.UpdateAttachmentsHandler; +import com.sap.cds.feature.attachments.handler.applicationservice.helper.ModifyApplicationHandlerHelper; import com.sap.cds.feature.attachments.handler.draftservice.DraftActiveAttachmentsHandler; import com.sap.cds.feature.attachments.handler.draftservice.DraftCancelAttachmentsHandler; import com.sap.cds.feature.attachments.handler.draftservice.DraftPatchAttachmentsHandler; @@ -253,6 +254,35 @@ void environmentHandlesNullExistingPaths() { "../target/cds/com.sap.cds/cds-feature-attachments/**"); } + @Test + void resolveDefaultMaxSize_returnsFiniteDefaultWhenNoConfig() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn(null); + + assertThat(Registration.resolveDefaultMaxSize(environment)) + .isEqualTo(ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); + } + + @Test + void resolveDefaultMaxSize_ignoresBlankConfig() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn(" "); + + assertThat(Registration.resolveDefaultMaxSize(environment)) + .isEqualTo(ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); + } + + @Test + void resolveDefaultMaxSize_honorsConfiguredOverride() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn("1GB"); + + assertThat(Registration.resolveDefaultMaxSize(environment)).isEqualTo("1GB"); + } + private void isHandlerForClassIncluded( List handlers, Class includedClass) { var isHandlerIncluded = diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java index 37ed28c8a..d93310607 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java @@ -77,7 +77,7 @@ void setup() { new CreateAttachmentsHandler( eventFactory, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, runtime); createContext = mock(CdsCreateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java index 8ea36c180..f41c39267 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java @@ -85,7 +85,7 @@ void setup() { attachmentsReader, attachmentService, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); event = mock(ModifyAttachmentEvent.class); updateContext = mock(CdsUpdateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java index 3c529434a..903db24dd 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java @@ -92,7 +92,7 @@ void serviceExceptionDueToContentLength() { eventContext, path, attachment.getContent(), - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER)); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE)); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); } @@ -147,7 +147,7 @@ void serviceExceptionDueToLimitExceeded() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER)); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE)); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); } @@ -179,7 +179,7 @@ void defaultValMaxValueUsed() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER)); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE)); } @Test @@ -211,7 +211,7 @@ void malformedContentLengthHeader() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER)); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE)); assertThat(exception.getErrorStatus()).isEqualTo(ErrorStatuses.BAD_REQUEST); } diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java index 035dd766b..746767750 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java @@ -63,7 +63,7 @@ void setup() { eventFactory = mock(ModifyAttachmentEventFactory.class); cut = new DraftPatchAttachmentsHandler( - persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); eventContext = mock(DraftPatchEventContext.class); event = mock(ModifyAttachmentEvent.class); when(eventFactory.getEvent(any(), any(), any())).thenReturn(event);