diff --git a/cliv2/go.mod b/cliv2/go.mod
index 6fe4f748a5..292cf7a319 100644
--- a/cliv2/go.mod
+++ b/cliv2/go.mod
@@ -9,9 +9,9 @@ require (
github.com/google/uuid v1.3.1
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.30.0
- github.com/snyk/cli-extension-dep-graph v0.0.0-20230821114325-956f460c443c
+ github.com/snyk/cli-extension-dep-graph v0.0.0-20230831101913-402a467e32e7
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce
- github.com/snyk/cli-extension-sbom v0.0.0-20230608154310-6573cedca977
+ github.com/snyk/cli-extension-sbom v0.0.0-20230831113416-7ffac8738181
github.com/snyk/go-application-framework v0.0.0-20230825084328-b839e0e50201
github.com/snyk/go-httpauth v0.0.0-20230726132335-d454674305a7
github.com/snyk/snyk-iac-capture v0.6.0
diff --git a/cliv2/go.sum b/cliv2/go.sum
index 512adc4d5e..d947a6e33c 100644
--- a/cliv2/go.sum
+++ b/cliv2/go.sum
@@ -613,12 +613,12 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
-github.com/snyk/cli-extension-dep-graph v0.0.0-20230821114325-956f460c443c h1:T9A/nUHxfxVrLWnlDg//FXa1Moj7gFJJexbPhHP5Wzc=
-github.com/snyk/cli-extension-dep-graph v0.0.0-20230821114325-956f460c443c/go.mod h1:QF3v8HBpOpyudYNCuR8LqfULutO76c91sBdLzD+pBJU=
+github.com/snyk/cli-extension-dep-graph v0.0.0-20230831101913-402a467e32e7 h1:V5b1Yz4Qjufs14bFoYBH6BRtfzTTCHeW32rV/9TiUY8=
+github.com/snyk/cli-extension-dep-graph v0.0.0-20230831101913-402a467e32e7/go.mod h1:QF3v8HBpOpyudYNCuR8LqfULutO76c91sBdLzD+pBJU=
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce h1:WchwuyPX4mEr7tFCGD6EsjwTDipFWfLxs4Wps6KB3b4=
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce/go.mod h1:5/IYYTgf32pST7St4GhS3KNz32WE17Ys+Hdb5Pqxex0=
-github.com/snyk/cli-extension-sbom v0.0.0-20230608154310-6573cedca977 h1:kW8XGQ3hseTP6WbGrO4Q6ssqGejT02hleNzxfWga72E=
-github.com/snyk/cli-extension-sbom v0.0.0-20230608154310-6573cedca977/go.mod h1:O/cjwCbKhJQWyXHPmNbZ7ToQKnhyw0VUp1Qhim3WEcw=
+github.com/snyk/cli-extension-sbom v0.0.0-20230831113416-7ffac8738181 h1:BMiPwr4/sD71Jfrvorgy+L2E7PCkbT36c4wo2N+BKPg=
+github.com/snyk/cli-extension-sbom v0.0.0-20230831113416-7ffac8738181/go.mod h1:O/cjwCbKhJQWyXHPmNbZ7ToQKnhyw0VUp1Qhim3WEcw=
github.com/snyk/go-application-framework v0.0.0-20230825084328-b839e0e50201 h1:5oHKA6Zs/c2oJ6MEznPn4grAJyedzltm9ms3o9ojU0Y=
github.com/snyk/go-application-framework v0.0.0-20230825084328-b839e0e50201/go.mod h1:Aun65T/AmzxjZe9jZZBqia6RHwoS7oq8QB2UfQIcPjU=
github.com/snyk/go-httpauth v0.0.0-20230726132335-d454674305a7 h1:m8C34vcouY2vEvow2gV/uAZ0LKiV7vhwC5HI15nUDX4=
diff --git a/test/fixtures/gradle-configurations/build.gradle b/test/fixtures/gradle-configurations/build.gradle
new file mode 100644
index 0000000000..ba08b5cfd7
--- /dev/null
+++ b/test/fixtures/gradle-configurations/build.gradle
@@ -0,0 +1,30 @@
+plugins {
+ id 'java-library'
+}
+
+group = 'com.example'
+version = '1.0'
+
+repositories {
+ mavenCentral()
+}
+
+def testAttribute = Attribute.of('test.snykattr', String)
+
+dependencies.attributesSchema {
+ attribute(testAttribute)
+}
+
+configurations {
+ api {
+ attributes {
+ attribute(testAttribute, 'api')
+ }
+ }
+}
+
+dependencies {
+ api 'org.jooq:jooq:3.18.6'
+ implementation 'com.google.guava:guava:23.0'
+ testImplementation 'junit:junit:4.+'
+}
diff --git a/test/fixtures/gradle-configurations/test-init.gradle b/test/fixtures/gradle-configurations/test-init.gradle
new file mode 100644
index 0000000000..e58bac063b
--- /dev/null
+++ b/test/fixtures/gradle-configurations/test-init.gradle
@@ -0,0 +1,7 @@
+allprojects {
+ plugins.withType(JavaPlugin) {
+ dependencies {
+ implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
+ }
+ }
+}
diff --git a/test/fixtures/gradle-multi-project/app/build.gradle b/test/fixtures/gradle-multi-project/app/build.gradle
new file mode 100644
index 0000000000..4107276d19
--- /dev/null
+++ b/test/fixtures/gradle-multi-project/app/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+ id 'java'
+}
+
+group = 'com.example'
+version = '1.0'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation project(':lib')
+ implementation 'com.fasterxml.jackson.core:jackson-core:2.15.2'
+}
diff --git a/test/fixtures/gradle-multi-project/build.gradle b/test/fixtures/gradle-multi-project/build.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/gradle-multi-project/lib/build.gradle b/test/fixtures/gradle-multi-project/lib/build.gradle
new file mode 100644
index 0000000000..ad7421f553
--- /dev/null
+++ b/test/fixtures/gradle-multi-project/lib/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+ id 'java-library'
+}
+
+group = 'com.example'
+version = '1.0'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ api 'org.jooq:jooq:3.18.6'
+ implementation 'com.google.guava:guava:23.0'
+ testImplementation 'junit:junit:4.+'
+}
\ No newline at end of file
diff --git a/test/fixtures/gradle-multi-project/settings.gradle b/test/fixtures/gradle-multi-project/settings.gradle
new file mode 100644
index 0000000000..a1b0e74981
--- /dev/null
+++ b/test/fixtures/gradle-multi-project/settings.gradle
@@ -0,0 +1,3 @@
+rootProject.name = 'example-multi-project'
+include 'app'
+include 'lib'
\ No newline at end of file
diff --git a/test/fixtures/maven-aggregate-project/core/pom.xml b/test/fixtures/maven-aggregate-project/core/pom.xml
new file mode 100644
index 0000000000..de39a516a0
--- /dev/null
+++ b/test/fixtures/maven-aggregate-project/core/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ parent-project
+ io.snyk
+ 1.0-SNAPSHOT
+
+
+ io.snyk
+ core
+ 1.0-SNAPSHOT
+
+ core
+
+ http://www.example.com
+
+
+ UTF-8
+
+
+
+
+ com.google.guava
+ guava
+ 29.0-jre
+
+
+
diff --git a/test/fixtures/maven-aggregate-project/extra/pom.xml b/test/fixtures/maven-aggregate-project/extra/pom.xml
new file mode 100644
index 0000000000..99adcdbd74
--- /dev/null
+++ b/test/fixtures/maven-aggregate-project/extra/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ parent-project
+ io.snyk
+ 1.0-SNAPSHOT
+
+
+ io.snyk
+ extra
+ 1.0-SNAPSHOT
+
+ extra
+
+ http://www.example.com
+
+
+ UTF-8
+
+
diff --git a/test/fixtures/maven-aggregate-project/pom.xml b/test/fixtures/maven-aggregate-project/pom.xml
new file mode 100644
index 0000000000..99c776813a
--- /dev/null
+++ b/test/fixtures/maven-aggregate-project/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ 4.0.0
+ io.snyk
+ parent-project
+ 1.0-SNAPSHOT
+ pom
+ parent-project
+
+ http://www.example.com
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+ service
+ core
+ extra
+
+
diff --git a/test/fixtures/maven-aggregate-project/service/pom.xml b/test/fixtures/maven-aggregate-project/service/pom.xml
new file mode 100644
index 0000000000..63ef128adf
--- /dev/null
+++ b/test/fixtures/maven-aggregate-project/service/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ parent-project
+ io.snyk
+ 1.0-SNAPSHOT
+
+
+ io.snyk
+ service
+ 1.0-SNAPSHOT
+
+ service
+
+ http://www.example.com
+
+
+ UTF-8
+
+
diff --git a/test/fixtures/maven-jars/keycloak-core-5.0.0.jar b/test/fixtures/maven-jars/keycloak-core-5.0.0.jar
new file mode 100644
index 0000000000..504e0a3daf
Binary files /dev/null and b/test/fixtures/maven-jars/keycloak-core-5.0.0.jar differ
diff --git a/test/fixtures/maven-jars/tomcat-http11-4.1.34.jar b/test/fixtures/maven-jars/tomcat-http11-4.1.34.jar
new file mode 100644
index 0000000000..28ba8d177c
Binary files /dev/null and b/test/fixtures/maven-jars/tomcat-http11-4.1.34.jar differ
diff --git a/test/jest/acceptance/snyk-sbom/common.ts b/test/jest/acceptance/snyk-sbom/common.ts
new file mode 100644
index 0000000000..d8cef91314
--- /dev/null
+++ b/test/jest/acceptance/snyk-sbom/common.ts
@@ -0,0 +1,31 @@
+import { createProjectFromFixture } from '../../util/createProject';
+import { runSnykCLI } from '../../util/runSnykCLI';
+
+export async function runSnykSbomCliCycloneDxJsonForFixture(
+ fixtureName: string,
+ options: string,
+ env: Record,
+): Promise {
+ const project = await createProjectFromFixture(fixtureName);
+
+ const { code, stdout, stderr } = await runSnykCLI(
+ `sbom --org=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format=cyclonedx1.4+json --debug ${options}`,
+ {
+ cwd: project.path(),
+ env,
+ },
+ );
+
+ if (code) {
+ console.log(stderr);
+ }
+
+ expect(code).toEqual(0);
+
+ let sbom;
+ expect(() => {
+ sbom = JSON.parse(stdout);
+ }).not.toThrow();
+
+ return sbom;
+}
diff --git a/test/jest/acceptance/snyk-sbom/gradle-options.spec.ts b/test/jest/acceptance/snyk-sbom/gradle-options.spec.ts
new file mode 100644
index 0000000000..a95ffb37ec
--- /dev/null
+++ b/test/jest/acceptance/snyk-sbom/gradle-options.spec.ts
@@ -0,0 +1,119 @@
+import { fakeServer } from '../../../acceptance/fake-server';
+import { runSnykSbomCliCycloneDxJsonForFixture } from './common';
+
+jest.setTimeout(1000 * 60 * 5);
+
+describe('snyk sbom: gradle options (mocked server only)', () => {
+ let server;
+ let env: Record;
+
+ beforeAll((done) => {
+ const port = process.env.PORT || process.env.SNYK_PORT || '58584';
+ const baseApi = '/api/v1';
+ env = {
+ ...process.env,
+ SNYK_API: 'http://localhost:' + port + baseApi,
+ SNYK_HOST: 'http://localhost:' + port,
+ SNYK_TOKEN: '123456789',
+ SNYK_DISABLE_ANALYTICS: '1',
+ };
+ server = fakeServer(baseApi, env.SNYK_TOKEN);
+ server.listen(port, () => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ server.restore();
+ });
+
+ afterAll((done) => {
+ server.close(() => {
+ done();
+ });
+ });
+
+ test('`sbom --sub-project=` generates an SBOM for selected sub-project', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-multi-project',
+ '--sub-project=lib',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toEqual('gradle-multi-project/lib');
+ expect(sbom.components.length).toBeGreaterThan(1);
+ });
+
+ test('`sbom --gradle-sub-project=` generates an SBOM for selected sub-project', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-multi-project',
+ '--gradle-sub-project=lib',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toEqual('gradle-multi-project/lib');
+ expect(sbom.components.length).toBeGreaterThan(1);
+ });
+
+ test('`sbom --all-sub-projects` generates an SBOM for all sub-projects', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-multi-project',
+ '--all-sub-projects',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toEqual('gradle-multi-project');
+ const listedComponentNames = sbom.components.map((it) => it.name);
+ expect(listedComponentNames).toContain('gradle-multi-project');
+ expect(listedComponentNames).toContain('gradle-multi-project/lib');
+ expect(listedComponentNames).toContain('gradle-multi-project/app');
+ });
+
+ test('`sbom --configuration-matching=` generates an SBOM only for matching configuration', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-configurations',
+ '--configuration-matching=^runtimeClasspath$',
+ env,
+ );
+
+ const listedComponentNames = sbom.components.map((it) => it.name);
+ expect(listedComponentNames).toContain('org.jooq:jooq');
+ expect(listedComponentNames).toContain('com.google.guava:guava');
+ expect(listedComponentNames).not.toContain('junit:junit');
+ });
+
+ test('`sbom --configuration-attributes=[,]...` generates an SBOM only for matching variant', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-configurations',
+ '--configuration-attributes=snykattr:api',
+ env,
+ );
+
+ // I failed to prepare an example where this works,
+ // so I will just test here that CLI executes successfully,
+ // meaning the command does not fail due to unrecognized config.
+ // To prepare the example I tried to set attribute on a configuration and filter by that,
+ // but I was always getting a full list of dependencies instead of a subset.
+ // The same I was getting when trying `snyk test` command.
+ // I was adding attributes as documented here: https://docs.gradle.org/current/userguide/variant_attributes.html
+ // I also tried using existing attribute `usage:java-api`, but also no luck.
+ expect(sbom.metadata.component.name).toEqual('gradle-configurations');
+ // const listedComponentNames = sbom.components.map((it) => it.name);
+ // expect(listedComponentNames).toContain('org.jooq:jooq');
+ // expect(listedComponentNames).not.toContain('com.google.guava:guava');
+ });
+
+ test('`sbom --init-script=` applies given init script', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'gradle-configurations',
+ '--init-script=test-init.gradle',
+ env,
+ );
+
+ const listedComponentNames = sbom.components.map((it) => it.name);
+ expect(listedComponentNames).toContain(
+ 'org.apache.logging.log4j:log4j-api',
+ ); // Dep added by init script
+ });
+});
diff --git a/test/jest/acceptance/snyk-sbom/maven-options.spec.ts b/test/jest/acceptance/snyk-sbom/maven-options.spec.ts
new file mode 100644
index 0000000000..776afd3056
--- /dev/null
+++ b/test/jest/acceptance/snyk-sbom/maven-options.spec.ts
@@ -0,0 +1,69 @@
+import { fakeServer } from '../../../acceptance/fake-server';
+import { runSnykSbomCliCycloneDxJsonForFixture } from './common';
+
+jest.setTimeout(1000 * 60 * 5);
+
+describe('snyk sbom: maven options (mocked server only)', () => {
+ let server;
+ let env: Record;
+
+ beforeAll((done) => {
+ const port = process.env.PORT || process.env.SNYK_PORT || '58584';
+ const baseApi = '/api/v1';
+ env = {
+ ...process.env,
+ SNYK_API: 'http://localhost:' + port + baseApi,
+ SNYK_HOST: 'http://localhost:' + port,
+ SNYK_TOKEN: '123456789',
+ SNYK_DISABLE_ANALYTICS: '1',
+ };
+ server = fakeServer(baseApi, env.SNYK_TOKEN);
+ server.listen(port, () => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ server.restore();
+ });
+
+ afterAll((done) => {
+ server.close(() => {
+ done();
+ });
+ });
+
+ test('`sbom --maven-aggregate-project` generates an SBOM for the multi-module maven project', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'maven-aggregate-project',
+ '--maven-aggregate-project',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toEqual('maven-aggregate-project');
+ expect(sbom.components.length).toBeGreaterThanOrEqual(11);
+ });
+
+ test('`sbom --scan-unmanaged --file=` generates an SBOM for the specific JAR file', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'maven-jars',
+ '--scan-unmanaged --file=tomcat-http11-4.1.34.jar',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toMatch(/^snyk-test-maven-jars-/);
+ expect(sbom.components.length).toBeGreaterThanOrEqual(2);
+ });
+
+ test('`sbom --scan-all-unmanaged` generates an SBOM for all the available JARs', async () => {
+ const sbom = await runSnykSbomCliCycloneDxJsonForFixture(
+ 'maven-jars',
+ '--scan-all-unmanaged',
+ env,
+ );
+
+ expect(sbom.metadata.component.name).toMatch(/^snyk-test-maven-jars-/);
+ expect(sbom.components.length).toBeGreaterThanOrEqual(3);
+ });
+});