Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;

/**
* Represents an environment and its relatedFiles
Expand Down Expand Up @@ -92,11 +93,11 @@ public List<InMemoryFile> getRelatedFiles() {
return relatedFiles;
}

public static CompleteEnvironment fromFile(File file) throws DeserializationException, InvalidFormatException, IOException {
public static CompleteEnvironment fromFile(File file) throws DeserializationException, InvalidFormatException, IOException, ZipBombException {
return fromInputStream(new FileInputStream(file), EnvironmentType.getFromFilePath(file.getPath()));
}

public static CompleteEnvironment fromInputStream(InputStream inputStream, EnvironmentType envType) throws DeserializationException, InvalidFormatException, IOException {
public static CompleteEnvironment fromInputStream(InputStream inputStream, EnvironmentType envType) throws DeserializationException, InvalidFormatException, IOException, ZipBombException {
Environment environment = null;
List<InMemoryFile> relatedFiles = null;

Expand All @@ -109,9 +110,16 @@ public static CompleteEnvironment fromInputStream(InputStream inputStream, Envir
environment = deserializer.read(inputStream);
}
if(envType == EnvironmentType.AASX) {
AASXDeserializer deserializer = new AASXDeserializer(inputStream);
relatedFiles = deserializer.getRelatedFiles();
environment = deserializer.read();
try {
AASXDeserializer deserializer = new AASXDeserializer(inputStream);
relatedFiles = deserializer.getRelatedFiles();
environment = deserializer.read();
} catch (Exception e) {
if (e.getMessage().startsWith("Zip bomb")) {
throw new ZipBombException(e);
}
throw e;
}
}

return new CompleteEnvironment(environment, relatedFiles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment;
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment.EnvironmentType;
import org.eclipse.digitaltwin.basyx.authorization.CommonAuthorizationProperties;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -77,7 +78,7 @@ public boolean shouldLoadPreconfiguredEnvironment() {
}

public void loadPreconfiguredEnvironments(AasEnvironment aasEnvironment)
throws IOException, DeserializationException, InvalidFormatException {
throws IOException, DeserializationException, InvalidFormatException, ZipBombException {
List<File> files = scanForEnvironments(pathsToLoad);

if (files.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void afterPropertiesSet() throws Exception {
loadPreconfiguredEnvironment();
}

private void loadPreconfiguredEnvironment() throws IOException, InvalidFormatException, DeserializationException {
private void loadPreconfiguredEnvironment() throws IOException, InvalidFormatException, DeserializationException, ZipBombException {
if (!preconfigurationLoader.shouldLoadPreconfiguredEnvironment()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.CrudConceptDescriptionRepositoryFactory;
import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.InMemoryConceptDescriptionBackend;
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository;
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository;
Expand Down Expand Up @@ -79,7 +80,7 @@ public void setUp() {
conceptDescriptionRepository = Mockito.spy(CrudConceptDescriptionRepositoryFactory.builder().backend(new InMemoryConceptDescriptionBackend()).create());
}

protected void loadRepositories(List<String> pathsToLoad) throws IOException, DeserializationException, InvalidFormatException {
protected void loadRepositories(List<String> pathsToLoad) throws IOException, DeserializationException, InvalidFormatException, ZipBombException {
DefaultAASEnvironment envLoader = new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository);

for (String path: pathsToLoad) {
Expand All @@ -89,7 +90,7 @@ protected void loadRepositories(List<String> pathsToLoad) throws IOException, De
}

@Test
public void testWithResourceFile_AllElementsAreDeployed() throws InvalidFormatException, IOException, DeserializationException {
public void testWithResourceFile_AllElementsAreDeployed() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
loadRepositories(List.of(TEST_ENVIRONMENT_JSON));

Assert.assertEquals(2, aasRepository.getAllAas(null, null, PaginationInfo.NO_LIMIT).getResult().size());
Expand All @@ -98,7 +99,7 @@ public void testWithResourceFile_AllElementsAreDeployed() throws InvalidFormatEx
}

@Test
public void testDeployedTwiceNoVersion_AllDeployedButNotOverriden() throws InvalidFormatException, IOException, DeserializationException {
public void testDeployedTwiceNoVersion_AllDeployedButNotOverriden() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
loadRepositories(List.of(TEST_ENVIRONMENT_JSON));
loadRepositories(List.of(TEST_ENVIRONMENT_JSON));

Expand All @@ -114,7 +115,7 @@ public void testDeployedTwiceNoVersion_AllDeployedButNotOverriden() throws Inval
}

@Test
public void testDeployedTwiceWithSameVersion_AllDeployedButNotOverriden() throws InvalidFormatException, IOException, DeserializationException {
public void testDeployedTwiceWithSameVersion_AllDeployedButNotOverriden() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
loadRepositories(List.of(TEST_ENVIRONMENT_VERSION_ON_SECOND_JSON));
loadRepositories(List.of(TEST_ENVIRONMENT_VERSION_ON_SECOND_JSON));

Expand All @@ -130,7 +131,7 @@ public void testDeployedTwiceWithSameVersion_AllDeployedButNotOverriden() throws
}

@Test
public void testDeployedTwiceNewRevision_ElementsAreOverriden() throws InvalidFormatException, IOException, DeserializationException {
public void testDeployedTwiceNewRevision_ElementsAreOverriden() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
loadRepositories(List.of(TEST_ENVIRONMENT_VERSION_ON_SECOND_JSON));
loadRepositories(List.of(TEST_ENVIRONMENT_VERSION_AND_REVISION_ON_SECOND_JSON));

Expand Down Expand Up @@ -159,7 +160,7 @@ public void testDuplicateShellIdsInEnvironments_ExceptionIsThrown() {
}

@Test
public void testWithResourceFile_NoExceptionsWhenReuploadAfterElementsAreRemoved() throws InvalidFormatException, IOException, DeserializationException {
public void testWithResourceFile_NoExceptionsWhenReuploadAfterElementsAreRemoved() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
AasEnvironment envLoader = new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository);

loadRepositoriesWithEnvironment(List.of(TEST_ENVIRONMENT_JSON), envLoader);
Expand All @@ -178,7 +179,7 @@ public void testWithResourceFile_NoExceptionsWhenReuploadAfterElementsAreRemoved
}

@Test
public void testWithResourceFile_ExceptionIsThrownWhenReuploadWithExistingElements() throws InvalidFormatException, IOException, DeserializationException {
public void testWithResourceFile_ExceptionIsThrownWhenReuploadWithExistingElements() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
AasEnvironment envLoader = new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository);

loadRepositoriesWithEnvironment(List.of(TEST_ENVIRONMENT_JSON), envLoader);
Expand All @@ -191,7 +192,7 @@ public void testWithResourceFile_ExceptionIsThrownWhenReuploadWithExistingElemen
Assert.assertThrows(expectedMsg, CollidingIdentifierException.class, () -> loadRepositoriesWithEnvironment(List.of(TEST_ENVIRONMENT_JSON), envLoader));
}

private void loadRepositoriesWithEnvironment(List<String> pathsToLoad, AasEnvironment aasEnvironment) throws IOException, DeserializationException, InvalidFormatException {
private void loadRepositoriesWithEnvironment(List<String> pathsToLoad, AasEnvironment aasEnvironment) throws IOException, DeserializationException, InvalidFormatException, ZipBombException {

for (String path: pathsToLoad) {
File file = rLoader.getResource(path).getFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment;
import org.eclipse.digitaltwin.basyx.aasenvironment.preconfiguration.AasEnvironmentPreconfigurationLoader;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
import org.junit.Assert;
import org.junit.Test;
Expand All @@ -45,13 +46,13 @@
public class PreconfigurationLoaderTextualResourceTest extends AasEnvironmentLoaderTest {

@Override
protected void loadRepositories(List<String> pathsToLoad) throws IOException, InvalidFormatException, DeserializationException {
protected void loadRepositories(List<String> pathsToLoad) throws IOException, InvalidFormatException, DeserializationException, ZipBombException {
AasEnvironmentPreconfigurationLoader envLoader = new AasEnvironmentPreconfigurationLoader(rLoader, pathsToLoad);
envLoader.loadPreconfiguredEnvironments(new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository));
}

@Test
public void testWithEmptyResource_NoElementsAreDeployed() throws InvalidFormatException, IOException, DeserializationException {
public void testWithEmptyResource_NoElementsAreDeployed() throws InvalidFormatException, IOException, DeserializationException, ZipBombException {
loadRepositories(List.of());
Assert.assertTrue(aasRepository.getAllAas(null, null, PaginationInfo.NO_LIMIT).getResult().isEmpty());
Assert.assertTrue(submodelRepository.getAllSubmodels(PaginationInfo.NO_LIMIT).getResult().isEmpty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.digitaltwin.basyx.client.internal.authorization.TokenManager;
import org.eclipse.digitaltwin.basyx.client.internal.authorization.grant.AccessTokenProvider;
import org.eclipse.digitaltwin.basyx.client.internal.authorization.grant.GrantType;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -104,7 +105,7 @@ public AuthorizedAASEnvironmentPreconfigurationLoader(ResourceLoader resourceLoa

@Override
public void loadPreconfiguredEnvironments(AasEnvironment aasEnvironment)
throws IOException, InvalidFormatException, DeserializationException {
throws IOException, InvalidFormatException, DeserializationException, ZipBombException {
if (isEnvironmentSet()) {
setUpTokenProvider();
configureSecurityContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.model.Result;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
Expand Down Expand Up @@ -83,5 +84,5 @@ ResponseEntity<Boolean> uploadEnvironment(
@Parameter(description = "An environment file (XML, JSON, AASX)") @Valid @RequestParam("file") MultipartFile envFile,
@Parameter(description = "Flag to indicate if already existing Ids should be ignored when reuploading an environment (default: false)", schema = @Schema(defaultValue = "false"))
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates
) throws IOException, InvalidFormatException, DeserializationException;
) throws IOException, InvalidFormatException, DeserializationException, ZipBombException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment;
import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment.EnvironmentType;
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
Expand Down Expand Up @@ -104,7 +105,7 @@ public ResponseEntity<Resource> generateSerializationByIds(
@Override
public ResponseEntity<Boolean> uploadEnvironment(
@RequestParam(value = "file") MultipartFile envFile,
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates) throws IOException, InvalidFormatException, DeserializationException {
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates) throws IOException, InvalidFormatException, DeserializationException, ZipBombException {
EnvironmentType envType = EnvironmentType.getFromMimeType(envFile.getContentType());

if (envType == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@

import java.util.List;

import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment;
import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironmentFactory;
import org.eclipse.digitaltwin.basyx.aasenvironment.feature.AasEnvironmentFeature;
import org.eclipse.digitaltwin.basyx.aasenvironment.feature.DecoratedAasEnvironmentFactory;
import org.eclipse.digitaltwin.basyx.aasenvironment.preconfiguration.AasEnvironmentPreconfigurationLoader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -48,7 +50,8 @@ public class AasEnvironmentConfiguration {

@Bean
@ConditionalOnMissingBean
public static AasEnvironment getAasEnvironment(AasEnvironmentFactory aasEnvironmentFactory, List<AasEnvironmentFeature> features) {
public static AasEnvironment getAasEnvironment(AasEnvironmentFactory aasEnvironmentFactory, List<AasEnvironmentFeature> features, @Value("${basyx.aasenvironment.minInflateRatio:0.1}") double minInflateRatio) {
ZipSecureFile.setMinInflateRatio(minInflateRatio);
return new DecoratedAasEnvironmentFactory(aasEnvironmentFactory, features).create();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ basyx.backend = InMemory
# basyx.cors.allowed-origins=http://localhost:3000, http://localhost:4000
# basyx.cors.allowed-methods=GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD

# basyx.aasenvironment.minInflateRatio=0.00001
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented example value of 0.00001 is extremely permissive and could allow ZIP bombs to expand 100,000 times (1/0.00001). This value would significantly weaken security protections. The comment should either: (1) Include documentation explaining what this ratio means (e.g., "Minimum ratio of compressed to uncompressed size; lower values allow more expansion"), (2) Show a more secure example value, or (3) Explain when and why someone would need to lower this value from the default of 1.0.

Suggested change
# basyx.aasenvironment.minInflateRatio=0.00001
# basyx.aasenvironment.minInflateRatio=1.0
# Minimum ratio of compressed to uncompressed size for uploaded ZIP files.
# Lower values allow more expansion and may increase risk of ZIP bomb attacks.
# Recommended: keep at 1.0 for maximum security. Only lower if you have a specific need and understand the risks.

Copilot uses AI. Check for mistakes.

####################################################################################
# Preconfiguring the Environment;
####################################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
import org.eclipse.digitaltwin.basyx.aasrepository.feature.registry.integration.AasRepositoryRegistryLink;
import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ZipBombException;
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository;
import org.eclipse.digitaltwin.basyx.submodelrepository.feature.registry.integration.SubmodelRepositoryRegistryLink;
Expand Down Expand Up @@ -95,7 +96,7 @@ public static void clearRegistries() throws Exception {
}

@Test
public void whenUploadDescriptorToRegistryFails_thenNoAasOrSmAreAddedToRepository() throws InvalidFormatException, DeserializationException, IOException, ApiException {
public void whenUploadDescriptorToRegistryFails_thenNoAasOrSmAreAddedToRepository() throws InvalidFormatException, DeserializationException, IOException, ApiException, ZipBombException {
// simulate descriptor already being in registry
aasRepositoryRegistryLink.getRegistryApi().postAssetAdministrationShellDescriptor(buildTestAasDescriptor());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.eclipse.digitaltwin.basyx.core.exceptions;

public class ZipBombException extends Exception{
public ZipBombException(Throwable th) {
super(th);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,9 @@ public ResponseEntity<Object> handleInvalidFormatException(InvalidFormatExceptio
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(ZipBombException.class)
public ResponseEntity<Object> handleZipBombException(ZipBombException exception) {
return buildResponse("Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\\nThis may indicate that the file is used to inflate memory usage and thus could pose a security risk.\\nYou can adjust this limit via basyx.aasenvironment.minInflateRatio in the AAS Environment configuration if you need to work with files which exceed this limit.", HttpStatus.BAD_REQUEST, exception);
}

}