From bfd05e38b54d78f3ad32ac81a6211c43a58e5ef6 Mon Sep 17 00:00:00 2001 From: COBI-98 Date: Thu, 3 Sep 2026 11:40:38 +0900 Subject: [PATCH 1/2] Add SBOM manifest attributes to repackaged Maven wars Packager#isCycloneDxBom only recognized CycloneDX SBOMs beneath META-INF/sbom/. In a war, the CycloneDX Maven plugin's output is packaged beneath WEB-INF/classes/META-INF/sbom/, so repackaging did not add the SBOM manifest attributes. Also check beneath Layout#getClassesLocation(), which findMainMethod already uses to locate application classes. Continue to support the root location used by jars. Signed-off-by: COBI-98 See gh-51551 --- .../boot/maven/WarIntegrationTests.java | 10 ++++ .../src/intTest/projects/war-sbom/pom.xml | 48 +++++++++++++++++++ .../main/java/org/test/SampleApplication.java | 24 ++++++++++ .../META-INF/sbom/application.cdx.json | 1 + .../boot/loader/tools/Packager.java | 4 +- .../loader/tools/AbstractPackagerTests.java | 15 ++++++ 6 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/pom.xml create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/java/org/test/SampleApplication.java create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/resources/META-INF/sbom/application.cdx.json diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java index 502e84041ce..b57aaf5a58c 100644 --- a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java @@ -67,6 +67,16 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { .hasAttribute("Not-Used", "Foo"))); } + @TestTemplate + void sbomManifestAttributesAreAddedToRepackagedWar(MavenBuild mavenBuild) { + mavenBuild.project("war-sbom").execute((project) -> { + File repackaged = new File(project, "target/war-sbom-0.0.1.BUILD-SNAPSHOT.war"); + assertThat(jar(repackaged)).hasEntryWithName("WEB-INF/classes/META-INF/sbom/application.cdx.json") + .manifest((manifest) -> manifest.hasAttribute("Sbom-Format", "CycloneDX") + .hasAttribute("Sbom-Location", "WEB-INF/classes/META-INF/sbom/application.cdx.json")); + }); + } + @TestTemplate void jarDependencyWithCustomFinalNameBuiltInSameReactorIsPackagedUsingArtifactIdAndVersion(MavenBuild mavenBuild) { mavenBuild.project("war-reactor") diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/pom.xml b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/pom.xml new file mode 100644 index 00000000000..745d5330321 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + org.springframework.boot.maven.it + war-sbom + 0.0.1.BUILD-SNAPSHOT + war + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + @maven-war-plugin.version@ + + + + + + org.springframework + spring-context + @spring-framework.version@ + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/java/org/test/SampleApplication.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/java/org/test/SampleApplication.java new file mode 100644 index 00000000000..547d0cf0171 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/resources/META-INF/sbom/application.cdx.json b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/resources/META-INF/sbom/application.cdx.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/war-sbom/src/main/resources/META-INF/sbom/application.cdx.json @@ -0,0 +1 @@ +{} diff --git a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java index 50754fa09c9..2e88ca53186 100644 --- a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java +++ b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java @@ -55,6 +55,7 @@ import org.springframework.util.StringUtils; * @author Stephane Nicoll * @author Madhura Bhave * @author Scott Frederick + * @author Cobi Eun * @since 2.3.0 */ public abstract class Packager { @@ -422,7 +423,8 @@ public abstract class Packager { } private boolean isCycloneDxBom(JarEntry entry) { - if (!entry.getName().startsWith("META-INF/sbom/")) { + if (!entry.getName().startsWith("META-INF/sbom/") + && !entry.getName().startsWith(getLayout().getClassesLocation() + "META-INF/sbom/")) { return false; } return entry.getName().endsWith(".cdx.json") || entry.getName().endsWith("/bom.json"); diff --git a/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java b/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java index 02a02d1befa..52bb7b98468 100644 --- a/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java +++ b/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java @@ -699,6 +699,21 @@ abstract class AbstractPackagerTests

{ .isEqualTo("META-INF/sbom/application.cdx.json"); } + @Test + void sbomManifestEntriesAreWrittenForWar() throws IOException { + this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class); + this.testJarFile.addFile("WEB-INF/classes/META-INF/sbom/application.cdx.json", + new ByteArrayInputStream(new byte[0])); + P packager = createPackager(this.testJarFile.getFile()); + packager.setLayout(new Layouts.War()); + execute(packager, NO_LIBRARIES); + Manifest manifest = getPackagedManifest(); + assertThat(manifest).isNotNull(); + assertThat(manifest.getMainAttributes().getValue("Sbom-Format")).isEqualTo("CycloneDX"); + assertThat(manifest.getMainAttributes().getValue("Sbom-Location")) + .isEqualTo("WEB-INF/classes/META-INF/sbom/application.cdx.json"); + } + private File createLibraryJar() throws IOException { TestJarFile library = new TestJarFile(this.tempDir); library.addClass("com/example/library/Library.class", ClassWithoutMainMethod.class); From 007ca201e14d7f03def15c5209762eff81b4e91c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 9 Sep 2026 14:44:51 +0100 Subject: [PATCH 2/2] Polish "Add SBOM manifest attributes to repackaged Maven wars" See gh-51551 Signed-off-by: Andy Wilkinson --- .../loader/tools/AbstractPackagerTests.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java b/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java index 52bb7b98468..0af59e83c54 100644 --- a/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java +++ b/loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/AbstractPackagerTests.java @@ -687,31 +687,26 @@ abstract class AbstractPackagerTests

{ } @Test - void sbomManifestEntriesAreWritten() throws IOException { - this.testJarFile.addClass("com/example/Application.class", ClassWithMainMethod.class); - this.testJarFile.addFile("META-INF/sbom/application.cdx.json", new ByteArrayInputStream(new byte[0])); - P packager = createPackager(this.testJarFile.getFile()); - execute(packager, NO_LIBRARIES); - Manifest manifest = getPackagedManifest(); - assertThat(manifest).isNotNull(); - assertThat(manifest.getMainAttributes().getValue("Sbom-Format")).isEqualTo("CycloneDX"); - assertThat(manifest.getMainAttributes().getValue("Sbom-Location")) - .isEqualTo("META-INF/sbom/application.cdx.json"); + void sbomManifestEntriesAreWrittenForJar() throws IOException { + sbomManifestEntriesAreWritten(new Layouts.Jar(), ""); } @Test void sbomManifestEntriesAreWrittenForWar() throws IOException { - this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class); - this.testJarFile.addFile("WEB-INF/classes/META-INF/sbom/application.cdx.json", - new ByteArrayInputStream(new byte[0])); + sbomManifestEntriesAreWritten(new Layouts.War(), "WEB-INF/classes/"); + } + + private void sbomManifestEntriesAreWritten(Layout layout, String prefix) throws IOException { + this.testJarFile.addClass(prefix + "com/example/Application.class", ClassWithMainMethod.class); + this.testJarFile.addFile(prefix + "META-INF/sbom/application.cdx.json", new ByteArrayInputStream(new byte[0])); P packager = createPackager(this.testJarFile.getFile()); - packager.setLayout(new Layouts.War()); + packager.setLayout(layout); execute(packager, NO_LIBRARIES); Manifest manifest = getPackagedManifest(); assertThat(manifest).isNotNull(); assertThat(manifest.getMainAttributes().getValue("Sbom-Format")).isEqualTo("CycloneDX"); assertThat(manifest.getMainAttributes().getValue("Sbom-Location")) - .isEqualTo("WEB-INF/classes/META-INF/sbom/application.cdx.json"); + .isEqualTo(prefix + "META-INF/sbom/application.cdx.json"); } private File createLibraryJar() throws IOException {