Skip to content

Commit

Permalink
feat: output additional SARIF data (#4966)
Browse files Browse the repository at this point in the history
* Added the following: artifacts scanned, CVSS score, package name, and fix information

* Updated unit tests

* Added an acceptance test

Co-authored-by: schottsfired <david.schott@snyk.io>
  • Loading branch information
bastiandoetsch and schottsfired committed Jan 15, 2024
1 parent c2eadce commit 8f1ff94
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 14 deletions.
29 changes: 24 additions & 5 deletions src/lib/formatters/get-sarif-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import * as sarif from 'sarif';
import * as groupBy from 'lodash.groupby';
import * as map from 'lodash.map';

import { SEVERITY, AnnotatedIssue } from '../snyk-test/legacy';
import { SEVERITY, TestResult, AnnotatedIssue } from '../snyk-test/legacy';

export function getResults(testResult): sarif.Result[] {
export function getResults(testResult: TestResult): sarif.Result[] {
const groupedVulnerabilities = groupBy(testResult.vulnerabilities, 'id');
return map(
groupedVulnerabilities,
([vuln]): sarif.Result => ({
([vuln]: AnnotatedIssue[]): sarif.Result => ({
ruleId: vuln.id,
level: getLevel(vuln),
message: {
Expand All @@ -27,8 +27,24 @@ export function getResults(testResult): sarif.Result[] {
startLine: vuln.lineNumber || 1,
},
},
logicalLocations: [
{
fullyQualifiedName: `${vuln.packageName}@${vuln.version}`,
},
],
},
],
fixes:
vuln.upgradePath?.length >= 2
? [
{
description: {
text: `Upgrade to ${vuln.upgradePath[1]}`,
},
artifactChanges: [],
},
]
: undefined,
}),
);
}
Expand All @@ -46,7 +62,10 @@ export function getLevel(vuln: AnnotatedIssue) {
}
}

function getArtifactLocationUri(targetFile: string, path: string): string {
function getArtifactLocationUri(
targetFile: string | undefined,
path: string | undefined,
): string {
if (targetFile) {
return targetFile;
}
Expand All @@ -55,5 +74,5 @@ function getArtifactLocationUri(targetFile: string, path: string): string {
// present. In this case we use the test result path which contains the image reference (e.g. alpine:3.18.0).
// Also, Github Code Scanning returns an error when the artifact location uri from the uploaded sarif file contains
// a colon (e.g. alpine:3.18.0 is not valid, but alpine_3.18.0 is valid), so we are replacing colon characters.
return path.replace(/:/g, '_');
return path ? path.replace(/:/g, '_') : '';
}
6 changes: 5 additions & 1 deletion src/lib/formatters/open-source-sarif-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export function createSarifOutputForOpenSource(
tool: {
driver: {
name: 'Snyk Open Source',
properties: {
artifactsScanned: testResult.dependencyCount,
},
rules: getRules(testResult),
},
},
Expand All @@ -52,7 +55,7 @@ export function getRules(testResult: TestResult): sarif.ReportingDescriptor[] {
const groupedVulnerabilities = groupBy(testResult.vulnerabilities, 'id');
return map(
groupedVulnerabilities,
([vuln, ...moreVulns]): sarif.ReportingDescriptor => {
([vuln, ...moreVulns]: AnnotatedIssue[]): sarif.ReportingDescriptor => {
const cves = vuln.identifiers?.CVE?.join();
return {
id: vuln.id,
Expand Down Expand Up @@ -84,6 +87,7 @@ ${vuln.description}`.replace(/##\s/g, '# '),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
testResult.packageManager!,
],
cvssv3_baseScore: vuln.cvssScore,
},
};
},
Expand Down
8 changes: 6 additions & 2 deletions src/lib/formatters/sarif-output.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as sarif from 'sarif';
import * as upperFirst from 'lodash.upperfirst';
import { TestResult } from '../snyk-test/legacy';
import { AnnotatedIssue, TestResult } from '../snyk-test/legacy';
import { SEVERITY } from '../snyk-test/legacy';
import { getResults } from './get-sarif-result';

Expand Down Expand Up @@ -36,6 +36,9 @@ export function getTool(testResult): sarif.Tool {
const tool: sarif.Tool = {
driver: {
name: 'Snyk Container',
properties: {
artifactsScanned: testResult.dependencyCount,
},
rules: [],
},
};
Expand All @@ -46,7 +49,7 @@ export function getTool(testResult): sarif.Tool {

const pushedIds = {};
tool.driver.rules = testResult.vulnerabilities
.map((vuln) => {
.map((vuln: AnnotatedIssue) => {
if (pushedIds[vuln.id]) {
return;
}
Expand Down Expand Up @@ -79,6 +82,7 @@ export function getTool(testResult): sarif.Tool {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
testResult.packageManager!,
],
cvssv3_baseScore: vuln.cvssScore,
},
};
})
Expand Down
3 changes: 3 additions & 0 deletions src/lib/snyk-test/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ interface AnnotatedIssue extends IssueData {
isPatchable: boolean;
severity: SEVERITY;
originalSeverity?: SEVERITY;
cvssScore?: number;
lineNumber?: number;

// These fields present for "node_module" based scans to allow remediation
bundled?: any;
Expand Down Expand Up @@ -183,6 +185,7 @@ export interface TestResult extends LegacyVulnApiResult {
foundProjectCount?: number;
scanResult?: ScanResult;
hasUnknownVersions?: boolean;
path?: string;
}

interface UpgradePathItem {
Expand Down
13 changes: 11 additions & 2 deletions test/fixtures/docker/sarif-container-result.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"tool": {
"driver": {
"name": "Snyk Container",
"properties": {
"artifactsScanned": 2
},
"rules": [
{
"id": "SNYK-LINUX-BZIP2-106947",
Expand All @@ -26,7 +29,8 @@
"tags": [
"security",
"deb"
]
],
"cvssv3_baseScore": 6.5
}
}
]
Expand All @@ -48,7 +52,12 @@
"region": {
"startLine": 1
}
}
},
"logicalLocations": [
{
"fullyQualifiedName": "bzip2@1.0.6-8.1"
}
]
}
]
}
Expand Down
13 changes: 11 additions & 2 deletions test/fixtures/docker/sarif-with-file-container-result.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"tool": {
"driver": {
"name": "Snyk Container",
"properties": {
"artifactsScanned": 2
},
"rules": [
{
"id": "SNYK-LINUX-BZIP2-106947",
Expand All @@ -26,7 +29,8 @@
"tags": [
"security",
"deb"
]
],
"cvssv3_baseScore": 6.5
}
}
]
Expand All @@ -48,7 +52,12 @@
"region": {
"startLine": 1
}
}
},
"logicalLocations": [
{
"fullyQualifiedName": "bzip2@1.0.6-8.1"
}
]
}
]
}
Expand Down
57 changes: 57 additions & 0 deletions test/jest/acceptance/snyk-test/output-formats/sarif.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createProjectFromFixture } from '../../../util/createProject';
import { runSnykCLI } from '../../../util/runSnykCLI';
import { fakeServer } from '../../../../acceptance/fake-server';

jest.setTimeout(1000 * 60);

describe('snyk test --sarif', () => {
let server;
let env: Record<string, string>;

beforeAll((done) => {
const port = process.env.PORT || process.env.SNYK_PORT || '12345';
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('`snyk test --sarif` includes the expected output', async () => {
const project = await createProjectFromFixture(
'npm/with-vulnerable-lodash-dep',
);
server.setDepGraphResponse(
await project.readJSON('test-dep-graph-result.json'),
);

const { code, stdout } = await runSnykCLI('test --sarif', {
cwd: project.path(),
env,
});
expect(code).toEqual(1);

expect(stdout).toContain('"artifactsScanned": 1');
expect(stdout).toContain('"cvssv3_baseScore": 5.3');
expect(stdout).toContain('"fullyQualifiedName": "lodash@4.17.15"');
expect(stdout).toContain('Upgrade to lodash@4.17.17');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,22 @@ Object {
Object {
"results": Array [
Object {
"fixes": Array [
Object {
"artifactChanges": Array [],
"description": Object {
"text": "Upgrade to jimp@0.2.28",
},
},
],
"level": "error",
"locations": Array [
Object {
"logicalLocations": Array [
Object {
"fullyQualifiedName": "ajv@6.12.2",
},
],
"physicalLocation": Object {
"artifactLocation": Object {
"uri": "package.json",
Expand All @@ -29,6 +42,9 @@ Object {
"tool": Object {
"driver": Object {
"name": "Snyk Open Source",
"properties": Object {
"artifactsScanned": 969,
},
"rules": Array [
Object {
"fullDescription": Object {
Expand All @@ -46,6 +62,7 @@ Object {
},
"id": "SNYK-JS-AJV-584908",
"properties": Object {
"cvssv3_baseScore": 7.5,
"tags": Array [
"security",
"CWE-400",
Expand Down

0 comments on commit 8f1ff94

Please sign in to comment.