From 4935cd530643084d551a7f3e00e5bbc4cd9ce68a Mon Sep 17 00:00:00 2001 From: Aayush Attri Date: Fri, 19 Dec 2025 08:49:34 +0000 Subject: [PATCH] feat: sbom traffic flows through os flows extension --- cliv2/go.mod | 6 +- cliv2/go.sum | 12 +- .../snyk-sbom-test/all-projects.spec.ts | 154 ------------------ ...bom-test-reachability-user-journey.spec.ts | 134 +++++++++++---- 4 files changed, 109 insertions(+), 197 deletions(-) delete mode 100644 test/jest/acceptance/snyk-sbom-test/all-projects.spec.ts diff --git a/cliv2/go.mod b/cliv2/go.mod index 0c4d9593ce..9c444831b8 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -15,10 +15,10 @@ require ( github.com/snyk/cli-extension-iac v0.0.0-20250829110702-b41ac109dab0 github.com/snyk/cli-extension-iac-rules v0.0.0-20250829110455-1260348bc188 github.com/snyk/cli-extension-mcp-scan v0.0.0-20251217093101-0705cbe3593b - github.com/snyk/cli-extension-os-flows v0.0.0-20251218084622-1754d1ef9945 - github.com/snyk/cli-extension-sbom v0.0.0-20251113132837-5f6cc6d0cb26 + github.com/snyk/cli-extension-os-flows v0.0.0-20260106151927-bc2b61c5f6e1 + github.com/snyk/cli-extension-sbom v0.0.0-20260106140701-6c590485fbe4 github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7 - github.com/snyk/error-catalog-golang-public v0.0.0-20251205100923-e93b06d4a6c6 + github.com/snyk/error-catalog-golang-public v0.0.0-20251222142433-dbdc288a6e98 github.com/snyk/go-application-framework v0.0.0-20260106115317-a1fb6f13accd github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 github.com/snyk/snyk-iac-capture v0.6.5 diff --git a/cliv2/go.sum b/cliv2/go.sum index 76111f0a91..455ee2203e 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -1306,18 +1306,18 @@ github.com/snyk/cli-extension-iac-rules v0.0.0-20250829110455-1260348bc188 h1:Uo github.com/snyk/cli-extension-iac-rules v0.0.0-20250829110455-1260348bc188/go.mod h1:qUc1yjKJe6tt/8/MJasnog3VBXd/b619MSFVfKAlDxE= github.com/snyk/cli-extension-mcp-scan v0.0.0-20251217093101-0705cbe3593b h1:d8s+TntutaQlPcB+5I2781ALWEgGfQh2XQjPrt0oRy8= github.com/snyk/cli-extension-mcp-scan v0.0.0-20251217093101-0705cbe3593b/go.mod h1:dRgGvQssSQ1U//nQ0D+H8JXnjz1ZhG9GWbz8GEaFRMQ= -github.com/snyk/cli-extension-os-flows v0.0.0-20251218084622-1754d1ef9945 h1:c/x1CGajnt9/sTcZ3IxGS9vnaN/SIwCsaMz7o4WmW1A= -github.com/snyk/cli-extension-os-flows v0.0.0-20251218084622-1754d1ef9945/go.mod h1:TWWxoMwavH+jAluWZtaaDbdOuwt8C5n51xnEuWvrv1g= -github.com/snyk/cli-extension-sbom v0.0.0-20251113132837-5f6cc6d0cb26 h1:KEiRBMdOJHefM4GKL3C3FfvH4J2G/vBFnwkonylV5+o= -github.com/snyk/cli-extension-sbom v0.0.0-20251113132837-5f6cc6d0cb26/go.mod h1:zyKDBaETfZyI7BfIjPnezH3QX2seQrR/d7NM5W6LV9s= +github.com/snyk/cli-extension-os-flows v0.0.0-20260106151927-bc2b61c5f6e1 h1:zNyzyrlCh5isS6pQAegNxmoclRdp9tmXmGxZWz5W3kw= +github.com/snyk/cli-extension-os-flows v0.0.0-20260106151927-bc2b61c5f6e1/go.mod h1:TWWxoMwavH+jAluWZtaaDbdOuwt8C5n51xnEuWvrv1g= +github.com/snyk/cli-extension-sbom v0.0.0-20260106140701-6c590485fbe4 h1:hfrkX61nZ0+RsVBBbsJNwjfga+tB0Nesa95a4dteqRc= +github.com/snyk/cli-extension-sbom v0.0.0-20260106140701-6c590485fbe4/go.mod h1:jIACVV10j4pW7LFrlYYtjn9mZm2JnXeFBM6/aTNJgvM= github.com/snyk/code-client-go v1.24.4 h1:19rmeqZFvjQMKaAmSZ0CdYZb1d0ENsDad2Cp32jeWOA= github.com/snyk/code-client-go v1.24.4/go.mod h1:uMlmMToe4uuNhNLs+yxjM3WFbytna+ytDWhpbnNwTSk= github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7 h1:/2+2piwQtB9fEJCkXEOjboZjY+77lQfnvqBZ/60xNHk= github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM= github.com/snyk/dep-graph/go v0.0.0-20251128083058-1972edcff6cf h1:RZ3KGLcbH37DUFNA0zDnfcET3jz6lxTOywON5L1xxK0= github.com/snyk/dep-graph/go v0.0.0-20251128083058-1972edcff6cf/go.mod h1:hTr91da/4ze2nk9q6ZW1BmfM2Z8rLUZSEZ3kK+6WGpc= -github.com/snyk/error-catalog-golang-public v0.0.0-20251205100923-e93b06d4a6c6 h1:8fGb/ipxqcmQHwC1ltd7Fch23fbQXP6kNohORe/GQpE= -github.com/snyk/error-catalog-golang-public v0.0.0-20251205100923-e93b06d4a6c6/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= +github.com/snyk/error-catalog-golang-public v0.0.0-20251222142433-dbdc288a6e98 h1:ucaLtBucnO9U8mrUmKovxObkflB1aQUQTB+orFt9rug= +github.com/snyk/error-catalog-golang-public v0.0.0-20251222142433-dbdc288a6e98/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= github.com/snyk/go-application-framework v0.0.0-20260106115317-a1fb6f13accd h1:mGFCdZOB+e7CdNbJ1+FezEVW9i7tc4PgC72bMwJmbxU= github.com/snyk/go-application-framework v0.0.0-20260106115317-a1fb6f13accd/go.mod h1:T+dt4+4XFAJ4PmoGgt/hrx7LiY+vaz+m9V4UYe24Rpc= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk= diff --git a/test/jest/acceptance/snyk-sbom-test/all-projects.spec.ts b/test/jest/acceptance/snyk-sbom-test/all-projects.spec.ts deleted file mode 100644 index 613c2e8836..0000000000 --- a/test/jest/acceptance/snyk-sbom-test/all-projects.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { runSnykCLI } from '../../util/runSnykCLI'; -import { fakeServer } from '../../../acceptance/fake-server'; -import { getServerPort } from '../../util/getServerPort'; -import { getFixturePath } from '../../util/getFixturePath'; -import * as path from 'path'; -import { EXIT_CODES } from '../../../../src/cli/exit-codes'; - -jest.setTimeout(1000 * 60); - -describe('snyk sbom test (mocked server only)', () => { - let server: ReturnType; - let env: Record; - - beforeAll((done) => { - const port = getServerPort(process); - const baseApi = ''; - env = { - ...process.env, - SNYK_API: 'http://localhost:' + port + baseApi, - SNYK_API_REST_URL: '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('npm CycloneDX JSON', async () => { - const fileToTest = path.resolve( - getFixturePath('sbom'), - 'npm-sbom-cdx15.json', - ); - - const { code, stdout, stderr } = await runSnykCLI( - `sbom test --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --experimental --file ${fileToTest}`, - { env }, - ); - - expect(stdout).toMatch( - '[MEDIUM] Regular Expression Denial of Service (ReDoS)', - ); - expect(stdout).toMatch('Introduced through: pkg:npm/minimatch@3.0.4'); - expect(stdout).toMatch( - 'URL: https://security.snyk.io/vuln/SNYK-JS-MINIMATCH-3050818', - ); - - expect(stdout).toMatch( - '[HIGH] Regular Expression Denial of Service (ReDoS)', - ); - expect(stdout).toMatch('Introduced through: pkg:npm/semver@7.3.5'); - expect(stdout).toMatch( - 'URL: https://security.snyk.io/vuln/SNYK-JS-SEMVER-3247795', - ); - expect(stdout).toMatch('[HIGH] GPL-2.0 license'); - expect(stdout).toMatch('Introduced through: pkg:npm/fuzzball@1.4.0'); - - expect(code).toEqual(1); - - expect(stderr).toEqual(''); - }); - - test('npm CycloneDX JSON with --json', async () => { - const fileToTest = path.resolve( - getFixturePath('sbom'), - 'npm-sbom-cdx15.json', - ); - - const { code, stdout, stderr } = await runSnykCLI( - `sbom test --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --experimental --file ${fileToTest} --json`, - { env }, - ); - - // Verify consistent timestamp format - expect(stdout).toContain('"disclosureTime":"2022-10-18T06:00:25Z",'); - expect(stdout).toContain('"publicationTime":"2022-10-18T06:29:18Z"'); - expect(stdout).toContain('"creationTime":"2022-10-18T06:10:47Z",'); - - // Verify other fields - expect(stdout).toContain( - '"CVSSv3":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",', - ); - expect(stdout).toContain( - '"title":"Regular Expression Denial of Service (ReDoS)",', - ); - expect(stdout).toContain('"version":"3.0.4",'); - expect(stdout).toContain('"name":"minimatch"'); - expect(stdout).toContain('"CWE":["CWE-1333"]'); - expect(stdout).toContain('"CVE":["CVE-2022-3517"]'); - expect(stdout).toContain('"semver":{"vulnerable":["3.0.4"]}'); - - expect(code).toEqual(1); - - expect(stderr).toEqual(''); - }); - - test('missing experimental flag', async () => { - const fileToTest = path.resolve( - getFixturePath('sbom'), - 'npm-sbom-cdx15.json', - ); - - const { stdout, stderr, code } = await runSnykCLI( - `sbom test --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --file ${fileToTest}`, - { env }, - ); - - expect(code).toBe(EXIT_CODES.ERROR); - - expect(stdout).toMatch('SNYK-CLI-0015'); - - expect(stderr).toEqual(''); - }); - - test('missing file flag', async () => { - const { stdout, stderr } = await runSnykCLI( - `sbom test --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --experimental`, - { env }, - ); - - expect(stdout).toContainText( - 'Flag `--file` is required to execute this command. Value should point to a valid SBOM document.', - ); - - expect(stderr).toEqual(''); - }); - - test('bad SBOM input', async () => { - const fileToTest = path.resolve(getFixturePath('sbom'), 'bad-sbom.json'); - const { code, stdout, stderr } = await runSnykCLI( - `sbom test --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --experimental --file ${fileToTest}`, - { env }, - ); - - expect(stdout).toMatch('Unknown SBOM format'); - expect(stdout).toMatch('SNYK-SBOM-0006'); - - expect(stderr).toEqual(''); - expect(code).toEqual(2); - }); -}); diff --git a/test/jest/acceptance/snyk-sbom-test/snyk-sbom-test-reachability-user-journey.spec.ts b/test/jest/acceptance/snyk-sbom-test/snyk-sbom-test-reachability-user-journey.spec.ts index f82ac13ac1..b51c1dc7bb 100644 --- a/test/jest/acceptance/snyk-sbom-test/snyk-sbom-test-reachability-user-journey.spec.ts +++ b/test/jest/acceptance/snyk-sbom-test/snyk-sbom-test-reachability-user-journey.spec.ts @@ -16,7 +16,6 @@ const SBOM_FILE_PATH = getFixturePath('sbom/snyk-goof-sbom.json'); const reachabilityEnv = { ...process.env, INTERNAL_SNYK_CLI_REACHABILITY_ENABLED: 'true', - INTERNAL_SNYK_CLI_SBOM_TEST_REACHABILITY: 'true', }; beforeAll(() => { @@ -50,49 +49,116 @@ afterAll(() => { } }); -describe('snyk sbom test --reachability', () => { - it('should display human-readable output with test summary', async () => { - const { code, stdout, stderr } = await runSnykCLI( - `sbom test --experimental --file=${SBOM_FILE_PATH} --reachability --source-dir=${TEMP_LOCAL_PATH}`, - { env: reachabilityEnv }, - ); +describe('snyk sbom test', () => { + describe('basic functionality', () => { + it('should display human-readable output with test summary', async () => { + const { code, stdout, stderr } = await runSnykCLI( + `sbom test --file=${SBOM_FILE_PATH}`, + ); + + expect(stderr).toBe(''); + expect(stdout).toContain('Test Summary'); + expect(stdout).toContain('Issues to fix by upgrading'); + expect(code).toBe(EXIT_CODES.VULNS_FOUND); + }); + + it('should output valid JSON with vulnerability data', async () => { + const { code, stdout, stderr } = await runSnykCLI( + `sbom test --file=${SBOM_FILE_PATH} --json`, + ); + + expect(stderr).toBe(''); + expect(stdout).not.toBe(''); + + const jsonOutput = JSON.parse(stdout); - expect(stderr).toBe(''); - expect(stdout).toContain('Test Summary'); - expect(code).toBe(EXIT_CODES.VULNS_FOUND); + expect(jsonOutput.dependencyCount).toBeGreaterThan(0); + expect(jsonOutput.vulnerabilities).toBeInstanceOf(Array); + expect(jsonOutput.vulnerabilities.length).toBeGreaterThanOrEqual(1); + expect( + Object.keys(jsonOutput.remediation.upgrade).length, + ).toBeGreaterThanOrEqual(1); + + const vuln = jsonOutput.vulnerabilities[0]; + expect(vuln).toHaveProperty('id'); + expect(vuln).toHaveProperty('title'); + expect(vuln).toHaveProperty('severity'); + + expect(code).toBe(EXIT_CODES.VULNS_FOUND); + }); + + it('should show error when --file flag is missing', async () => { + const { code, stdout } = await runSnykCLI(`sbom test`); + + expect(stdout).toContain('--file'); + expect(code).toBe(EXIT_CODES.ERROR); + }); }); - it('should output valid JSON with reachability data', async () => { - const { code, stdout, stderr } = await runSnykCLI( - `sbom test --experimental --file=${SBOM_FILE_PATH} --reachability --source-dir=${TEMP_LOCAL_PATH} --json`, - { env: reachabilityEnv }, - ); + describe('with reachability', () => { + it('should display human-readable output with test summary', async () => { + const { code, stdout, stderr } = await runSnykCLI( + `sbom test --file=${SBOM_FILE_PATH} --reachability --source-dir=${TEMP_LOCAL_PATH}`, + { env: reachabilityEnv }, + ); - expect(stderr).toBe(''); - expect(stdout).not.toBe(''); + expect(stderr).toBe(''); + expect(stdout).toContain('Test Summary'); + expect(code).toBe(EXIT_CODES.VULNS_FOUND); + }); - const jsonOutput = JSON.parse(stdout); + it('should emit valid json output with filtering only reachable vulnerabilities', async () => { + const { code, stdout, stderr } = await runSnykCLI( + `sbom test --file=${SBOM_FILE_PATH} --reachability --source-dir=${TEMP_LOCAL_PATH} --reachability-filter=reachable --json`, + { env: reachabilityEnv }, + ); - expect(jsonOutput.dependencyCount).toBeGreaterThan(0); - expect(jsonOutput.vulnerabilities).toBeInstanceOf(Array); - expect(jsonOutput.vulnerabilities.length).toBeGreaterThanOrEqual(1); + expect(stdout).not.toBe(''); + expect(stderr).toBe(''); - const vulnsWithReachability = jsonOutput.vulnerabilities.filter( - (vuln: any) => vuln.reachability !== undefined, - ); - expect(vulnsWithReachability.length).toBeGreaterThan(0); + const jsonOutput = JSON.parse(stdout); - const reachableVulns = jsonOutput.vulnerabilities.filter( - (vuln: any) => vuln.reachability === 'reachable', - ); + const areAllVulnsReachable = jsonOutput.vulnerabilities.every( + (vuln: { reachability: string }) => vuln.reachability === 'reachable', + ); + + expect(jsonOutput.vulnerabilities.length).toBeGreaterThanOrEqual(1); + expect(areAllVulnsReachable).toBeTruthy(); + expect(code).toBe(EXIT_CODES.VULNS_FOUND); + }); + + it('should output valid JSON with reachability data', async () => { + const { code, stdout, stderr } = await runSnykCLI( + `sbom test --file=${SBOM_FILE_PATH} --reachability --source-dir=${TEMP_LOCAL_PATH} --json`, + { env: reachabilityEnv }, + ); + + expect(stderr).toBe(''); + expect(stdout).not.toBe(''); + + const jsonOutput = JSON.parse(stdout); + + expect(jsonOutput.dependencyCount).toBeGreaterThan(0); + expect(jsonOutput.vulnerabilities).toBeInstanceOf(Array); + expect(jsonOutput.vulnerabilities.length).toBeGreaterThanOrEqual(1); + + const vulnsWithReachability = jsonOutput.vulnerabilities.filter( + (vuln: any) => vuln.reachability !== undefined, + ); + expect(vulnsWithReachability.length).toBeGreaterThan(0); + + const reachableVulns = jsonOutput.vulnerabilities.filter( + (vuln: any) => vuln.reachability === 'reachable', + ); - expect(reachableVulns.length).toBeGreaterThan(0); + expect(reachableVulns.length).toBeGreaterThan(0); - expect(reachableVulns[0]).toHaveProperty('id'); - expect(reachableVulns[0]).toHaveProperty('title'); - expect(reachableVulns[0]).toHaveProperty('severity'); - expect(reachableVulns[0]).toHaveProperty('reachability', 'reachable'); + expect(reachableVulns[0]).toHaveProperty('id'); + expect(reachableVulns[0]).toHaveProperty('title'); + expect(reachableVulns[0]).toHaveProperty('severity'); + expect(reachableVulns[0]).toHaveProperty('reachability', 'reachable'); - expect(code).toBe(EXIT_CODES.VULNS_FOUND); + expect(code).toBe(EXIT_CODES.VULNS_FOUND); + }); }); });