@fabasoad/sarif-to-slack 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/pull_request_template.md +3 -3
- package/.github/workflows/linting.yml +14 -0
- package/.github/workflows/release.yml +5 -1
- package/.github/workflows/send-sarif-to-slack.yml +214 -0
- package/.github/workflows/unit-tests.yml +1 -0
- package/.pre-commit-config.yaml +3 -3
- package/.tool-versions +1 -1
- package/CONTRIBUTING.md +1 -1
- package/Makefile +10 -3
- package/README.md +36 -5
- package/biome.json +15 -12
- package/dist/Logger.js +17 -6
- package/dist/Processors.js +23 -22
- package/dist/SarifToSlackService.d.ts.map +1 -1
- package/dist/SarifToSlackService.js +6 -7
- package/dist/SlackMessageBuilder.js +51 -55
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/model/SarifModelPerRun.d.ts +17 -0
- package/dist/model/SarifModelPerRun.d.ts.map +1 -0
- package/dist/model/SarifModelPerRun.js +84 -0
- package/dist/model/SarifModelPerSarif.d.ts +20 -0
- package/dist/model/SarifModelPerSarif.d.ts.map +1 -0
- package/dist/model/SarifModelPerSarif.js +97 -0
- package/dist/model/types.d.ts +17 -0
- package/dist/model/types.d.ts.map +1 -0
- package/dist/model/types.js +31 -0
- package/dist/sarif-to-slack.d.ts +121 -18
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types.d.ts +107 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +73 -7
- package/dist/utils/SarifUtils.d.ts +5 -0
- package/dist/utils/SarifUtils.d.ts.map +1 -0
- package/dist/utils/SarifUtils.js +32 -0
- package/dist/utils/SortUtils.d.ts +5 -0
- package/dist/utils/SortUtils.d.ts.map +1 -0
- package/dist/utils/SortUtils.js +8 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/etc/sarif-to-slack.api.md +47 -9
- package/jest.config.json +4 -4
- package/package.json +14 -10
- package/scripts/save-version.sh +6 -0
- package/src/Logger.ts +22 -17
- package/src/Processors.ts +22 -22
- package/src/SarifToSlackService.ts +6 -7
- package/src/SlackMessageBuilder.ts +85 -68
- package/src/index.ts +17 -6
- package/src/model/SarifModelPerRun.ts +114 -0
- package/src/model/SarifModelPerSarif.ts +116 -0
- package/src/model/types.ts +31 -0
- package/src/types.ts +113 -15
- package/src/utils/SarifUtils.ts +44 -0
- package/src/utils/SortUtils.ts +21 -0
- package/src/version.ts +3 -0
- package/test-data/sarif/codeql-csharp.sarif +1 -0
- package/test-data/sarif/codeql-go.sarif +1 -0
- package/test-data/sarif/codeql-python.sarif +1 -0
- package/test-data/sarif/codeql-ruby.sarif +1 -0
- package/test-data/sarif/codeql-typescript.sarif +1 -0
- package/test-data/sarif/grype-container.sarif +1774 -0
- package/test-data/sarif/runs-1-tools-1-results-0.sarif +18 -0
- package/test-data/sarif/runs-2-tools-1-results-0.sarif +30 -0
- package/test-data/sarif/runs-2-tools-1.sarif +656 -0
- package/test-data/sarif/runs-2-tools-2-results-0.sarif +44 -0
- package/test-data/sarif/runs-2-tools-2.sarif +686 -0
- package/test-data/sarif/runs-3-tools-2-results-0.sarif +48 -0
- package/test-data/sarif/runs-3-tools-2.sarif +278 -0
- package/test-data/sarif/snyk-composer.sarif +934 -0
- package/test-data/sarif/snyk-container.sarif +313 -0
- package/test-data/sarif/snyk-gomodules.sarif +388 -0
- package/test-data/sarif/snyk-gradle.sarif +274 -0
- package/test-data/sarif/snyk-hex.sarif +66 -0
- package/test-data/sarif/snyk-maven.sarif +274 -0
- package/test-data/sarif/snyk-npm.sarif +896 -0
- package/test-data/sarif/snyk-nuget.sarif +90 -0
- package/test-data/sarif/snyk-pip.sarif +66 -0
- package/test-data/sarif/snyk-pnpm.sarif +90 -0
- package/test-data/sarif/snyk-poetry.sarif +1952 -0
- package/test-data/sarif/snyk-rubygems.sarif +440 -0
- package/test-data/sarif/snyk-sbt.sarif +178 -0
- package/test-data/sarif/snyk-swift.sarif +112 -0
- package/test-data/sarif/snyk-yarn.sarif +2900 -0
- package/test-data/sarif/trivy-iac.sarif +134 -0
- package/test-data/sarif/wiz-container.sarif +30916 -0
- package/test-data/sarif/wiz-iac.sarif +558 -0
- package/tests/Processors.spec.ts +3 -3
- package/tests/integration/SendSarifToSlack.spec.ts +56 -0
- package/tsconfig.json +14 -14
- package/dist/Logger.js.map +0 -1
- package/dist/Processors.js.map +0 -1
- package/dist/SarifToSlackService.js.map +0 -1
- package/dist/SlackMessageBuilder.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Result, Run } from 'sarif';
|
|
2
|
+
import { tryGetRulePropertyByResult } from '../utils/SarifUtils'
|
|
3
|
+
import { SecurityLevel, SecuritySeverity } from './types'
|
|
4
|
+
import Logger from '../Logger'
|
|
5
|
+
import { Map as ImmutableMap } from 'immutable'
|
|
6
|
+
import {
|
|
7
|
+
sortSecurityLevelMap,
|
|
8
|
+
sortSecuritySeverityMap
|
|
9
|
+
} from '../utils/SortUtils';
|
|
10
|
+
|
|
11
|
+
export class SarifModelPerRun {
|
|
12
|
+
public readonly toolName: string
|
|
13
|
+
|
|
14
|
+
private readonly _securitySeverityMap: ImmutableMap<SecuritySeverity, number>
|
|
15
|
+
private readonly _securityLevelMap: ImmutableMap<SecurityLevel, number>
|
|
16
|
+
|
|
17
|
+
constructor(run: Run) {
|
|
18
|
+
this.toolName = run.tool.driver.name
|
|
19
|
+
|
|
20
|
+
this._securitySeverityMap = ImmutableMap<SecuritySeverity, number>().asMutable()
|
|
21
|
+
this._securityLevelMap = ImmutableMap<SecurityLevel, number>().asMutable()
|
|
22
|
+
|
|
23
|
+
this.buildSecuritySeverityMap(run)
|
|
24
|
+
this.buildSecurityLevelMap(run)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private identifySecuritySeverity(score?: number): SecuritySeverity {
|
|
28
|
+
if (score === undefined) {
|
|
29
|
+
return SecuritySeverity.Unknown
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (score >= 9 && score <= 10) {
|
|
33
|
+
return SecuritySeverity.Critical
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (score >= 7) {
|
|
37
|
+
return SecuritySeverity.High
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (score >= 4) {
|
|
41
|
+
return SecuritySeverity.Medium
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (score >= 0.1) {
|
|
45
|
+
return SecuritySeverity.Low
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (score == 0) {
|
|
49
|
+
return SecuritySeverity.None
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Logger.warn(`Unsupported "${score}" security severity. Saving as "Unknown".`)
|
|
53
|
+
return SecuritySeverity.Unknown
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private identifySecurityLevel(level?: string): SecurityLevel {
|
|
57
|
+
if (level === undefined) {
|
|
58
|
+
return SecurityLevel.Unknown
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (level.toLowerCase() === 'error') {
|
|
62
|
+
return SecurityLevel.Error
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (level.toLowerCase() === 'warning') {
|
|
66
|
+
return SecurityLevel.Warning
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (level.toLowerCase() === 'note') {
|
|
70
|
+
return SecurityLevel.Note
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Logger.warn(`Unsupported ${level} security level. Saving as "Unknown".`)
|
|
74
|
+
return SecurityLevel.Unknown
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private buildSecuritySeverityMap(run: Run): void {
|
|
78
|
+
const results: Result[] = run.results ?? []
|
|
79
|
+
for (const result of results) {
|
|
80
|
+
const severity: SecuritySeverity = this.identifySecuritySeverity(
|
|
81
|
+
tryGetRulePropertyByResult(run, result, 'security-severity')
|
|
82
|
+
)
|
|
83
|
+
const count: number = this._securitySeverityMap.get(severity) || 0
|
|
84
|
+
this._securitySeverityMap.set(severity, count + 1)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private tryGetSecurityLevel(run: Run, result: Result): string | undefined {
|
|
89
|
+
if (result.level) {
|
|
90
|
+
return result.level
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return tryGetRulePropertyByResult(run, result, 'problem.severity')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private buildSecurityLevelMap(run: Run): void {
|
|
97
|
+
const results: Result[] = run.results ?? []
|
|
98
|
+
for (const result of results) {
|
|
99
|
+
const level: SecurityLevel = this.identifySecurityLevel(
|
|
100
|
+
this.tryGetSecurityLevel(run, result)
|
|
101
|
+
)
|
|
102
|
+
const count: number = this._securityLevelMap.get(level) || 0
|
|
103
|
+
this._securityLevelMap.set(level, count + 1)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public get securitySeverityMap(): ImmutableMap<SecuritySeverity, number> {
|
|
108
|
+
return sortSecuritySeverityMap(this._securitySeverityMap)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public get securityLevelMap(): ImmutableMap<SecurityLevel, number> {
|
|
112
|
+
return sortSecurityLevelMap(this._securityLevelMap)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { SarifLog } from '../types'
|
|
2
|
+
import { Map as ImmutableMap } from 'immutable'
|
|
3
|
+
import { SarifModelPerRun } from './SarifModelPerRun'
|
|
4
|
+
import { SecurityLevel, SecuritySeverity } from './types'
|
|
5
|
+
import {
|
|
6
|
+
sortSecurityLevelMap,
|
|
7
|
+
sortSecuritySeverityMap
|
|
8
|
+
} from '../utils/SortUtils';
|
|
9
|
+
|
|
10
|
+
export type DataGroupedByRun<T> = {
|
|
11
|
+
toolName: string,
|
|
12
|
+
data: ImmutableMap<T, number>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SarifModelPerSarif {
|
|
16
|
+
private readonly sarifModelPerRunList: Array<SarifModelPerRun>;
|
|
17
|
+
|
|
18
|
+
constructor(sarif: SarifLog) {
|
|
19
|
+
this.sarifModelPerRunList = new Array<SarifModelPerRun>()
|
|
20
|
+
this.buildRunsList(sarif)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private buildRunsList(sarif: SarifLog): void {
|
|
24
|
+
for (const run of sarif.runs) {
|
|
25
|
+
this.sarifModelPerRunList.push(new SarifModelPerRun(run))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public groupByToolNameWithSecurityLevel(): Map<string, ImmutableMap<SecurityLevel, number>> {
|
|
30
|
+
const result = new Map<string, ImmutableMap<SecurityLevel, number>>()
|
|
31
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
32
|
+
if (!result.has(sarifModelPerRun.toolName)) {
|
|
33
|
+
result.set(sarifModelPerRun.toolName, ImmutableMap<SecurityLevel, number>().asMutable())
|
|
34
|
+
}
|
|
35
|
+
for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
|
|
36
|
+
const count: number = result.get(sarifModelPerRun.toolName)?.get(k) || 0
|
|
37
|
+
result.get(sarifModelPerRun.toolName)?.set(k, count + v)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Sort
|
|
41
|
+
for (const [k, v] of result) {
|
|
42
|
+
result.set(k, sortSecurityLevelMap(v))
|
|
43
|
+
}
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public groupByRunWithSecurityLevel(): DataGroupedByRun<SecurityLevel>[] {
|
|
48
|
+
const result = new Array<DataGroupedByRun<SecurityLevel>>()
|
|
49
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
50
|
+
result.push({
|
|
51
|
+
toolName: sarifModelPerRun.toolName,
|
|
52
|
+
data: sarifModelPerRun.securityLevelMap,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
return result
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public groupByTotalWithSecurityLevel(): ImmutableMap<SecurityLevel, number> {
|
|
59
|
+
const result = ImmutableMap<SecurityLevel, number>().asMutable()
|
|
60
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
61
|
+
for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
|
|
62
|
+
const count: number = result.get(k) || 0
|
|
63
|
+
result.set(k, count + v)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sortSecurityLevelMap(result)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public groupByToolNameWithSecuritySeverity(): Map<string, ImmutableMap<SecuritySeverity, number>> {
|
|
70
|
+
const result = new Map<string, ImmutableMap<SecuritySeverity, number>>()
|
|
71
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
72
|
+
if (!result.has(sarifModelPerRun.toolName)) {
|
|
73
|
+
result.set(sarifModelPerRun.toolName, ImmutableMap<SecuritySeverity, number>().asMutable())
|
|
74
|
+
}
|
|
75
|
+
for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
|
|
76
|
+
const count: number = result.get(sarifModelPerRun.toolName)?.get(k) || 0
|
|
77
|
+
result.get(sarifModelPerRun.toolName)?.set(k, count + v)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Sort
|
|
81
|
+
for (const [k, v] of result.entries()) {
|
|
82
|
+
result.set(k, sortSecuritySeverityMap(v))
|
|
83
|
+
}
|
|
84
|
+
return result
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public groupByRunWithSecuritySeverity(): DataGroupedByRun<SecuritySeverity>[] {
|
|
88
|
+
const result = new Array<DataGroupedByRun<SecuritySeverity>>()
|
|
89
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
90
|
+
result.push({
|
|
91
|
+
toolName: sarifModelPerRun.toolName,
|
|
92
|
+
data: sarifModelPerRun.securitySeverityMap,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public groupByTotalWithSecuritySeverity(): ImmutableMap<SecuritySeverity, number> {
|
|
99
|
+
const result = ImmutableMap<SecuritySeverity, number>().asMutable()
|
|
100
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
101
|
+
for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
|
|
102
|
+
const count: number = result.get(k) || 0
|
|
103
|
+
result.set(k, count + v)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return sortSecuritySeverityMap(result)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public listToolNames(): Set<string> {
|
|
110
|
+
const toolNames = new Set<string>()
|
|
111
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
112
|
+
toolNames.add(sarifModelPerRun.toolName)
|
|
113
|
+
}
|
|
114
|
+
return toolNames
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export enum SecuritySeverity {
|
|
2
|
+
Unknown = 'Unknown',
|
|
3
|
+
None = 'None',
|
|
4
|
+
Low = 'Low',
|
|
5
|
+
Medium = 'Medium',
|
|
6
|
+
High = 'High',
|
|
7
|
+
Critical = 'Critical'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SecuritySeverityOrder: SecuritySeverity[] = [
|
|
11
|
+
SecuritySeverity.Critical,
|
|
12
|
+
SecuritySeverity.High,
|
|
13
|
+
SecuritySeverity.Medium,
|
|
14
|
+
SecuritySeverity.Low,
|
|
15
|
+
SecuritySeverity.None,
|
|
16
|
+
SecuritySeverity.Unknown
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export enum SecurityLevel {
|
|
20
|
+
Unknown = 'Unknown',
|
|
21
|
+
Note = 'Note',
|
|
22
|
+
Warning = 'Warning',
|
|
23
|
+
Error = 'Error'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const SecurityLevelOrder: SecurityLevel[] = [
|
|
27
|
+
SecurityLevel.Error,
|
|
28
|
+
SecurityLevel.Warning,
|
|
29
|
+
SecurityLevel.Note,
|
|
30
|
+
SecurityLevel.Unknown
|
|
31
|
+
]
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Log } from 'sarif'
|
|
|
4
4
|
* Type representing a SARIF log.
|
|
5
5
|
* @public
|
|
6
6
|
*/
|
|
7
|
-
export type
|
|
7
|
+
export type SarifLog = Log
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Interface for a Slack message that can be sent.
|
|
@@ -19,7 +19,7 @@ export interface SlackMessage {
|
|
|
19
19
|
/**
|
|
20
20
|
* The SARIF log associated with this Slack message.
|
|
21
21
|
*/
|
|
22
|
-
sarif:
|
|
22
|
+
sarif: SarifLog
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -28,7 +28,8 @@ export interface SlackMessage {
|
|
|
28
28
|
*/
|
|
29
29
|
export enum LogLevel {
|
|
30
30
|
/**
|
|
31
|
-
* Represents the most verbose logging level, typically used for detailed
|
|
31
|
+
* Represents the most verbose logging level, typically used for detailed
|
|
32
|
+
* debugging information.
|
|
32
33
|
*/
|
|
33
34
|
Silly = 0,
|
|
34
35
|
/**
|
|
@@ -36,23 +37,28 @@ export enum LogLevel {
|
|
|
36
37
|
*/
|
|
37
38
|
Trace = 1,
|
|
38
39
|
/**
|
|
39
|
-
* Represents a logging level for debugging information that is less verbose
|
|
40
|
+
* Represents a logging level for debugging information that is less verbose
|
|
41
|
+
* than silly.
|
|
40
42
|
*/
|
|
41
43
|
Debug = 2,
|
|
42
44
|
/**
|
|
43
|
-
* Represents a logging level for general informational messages that highlight
|
|
45
|
+
* Represents a logging level for general informational messages that highlight
|
|
46
|
+
* the progress of the application.
|
|
44
47
|
*/
|
|
45
48
|
Info = 3,
|
|
46
49
|
/**
|
|
47
|
-
* Represents a logging level for potentially harmful situations that require
|
|
50
|
+
* Represents a logging level for potentially harmful situations that require
|
|
51
|
+
* attention.
|
|
48
52
|
*/
|
|
49
53
|
Warning = 4,
|
|
50
54
|
/**
|
|
51
|
-
* Represents a logging level for error conditions that do not require immediate
|
|
55
|
+
* Represents a logging level for error conditions that do not require immediate
|
|
56
|
+
* action but should be noted.
|
|
52
57
|
*/
|
|
53
58
|
Error = 5,
|
|
54
59
|
/**
|
|
55
|
-
* Represents a logging level for critical errors that require immediate attention
|
|
60
|
+
* Represents a logging level for critical errors that require immediate attention
|
|
61
|
+
* and may cause the application to terminate.
|
|
56
62
|
*/
|
|
57
63
|
Fatal = 6
|
|
58
64
|
}
|
|
@@ -62,7 +68,7 @@ export enum LogLevel {
|
|
|
62
68
|
* in the Slack message.
|
|
63
69
|
* @public
|
|
64
70
|
*/
|
|
65
|
-
export type
|
|
71
|
+
export type IncludeAwareOptions = {
|
|
66
72
|
include: boolean
|
|
67
73
|
}
|
|
68
74
|
|
|
@@ -71,10 +77,101 @@ export type IncludeAwareProps = {
|
|
|
71
77
|
* in the Slack message, along with an optional value.
|
|
72
78
|
* @public
|
|
73
79
|
*/
|
|
74
|
-
export type
|
|
80
|
+
export type IncludeAwareWithValueOptions = IncludeAwareOptions & {
|
|
75
81
|
value?: string
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Enum representing the type of footer in a Slack message.
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
export enum FooterType {
|
|
89
|
+
/**
|
|
90
|
+
* Represents a plain text footer. Text is not formatted and appears as-is.
|
|
91
|
+
*/
|
|
92
|
+
PlainText = 'plain_text',
|
|
93
|
+
/**
|
|
94
|
+
* Represents a footer with Markdown formatting. Text can include formatting
|
|
95
|
+
* such as bold, italics, and links.
|
|
96
|
+
*/
|
|
97
|
+
Markdown = 'mrkdwn'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options for the footer of a Slack message. "type" is ignored if "value" is
|
|
102
|
+
* not defined.
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export type FooterOptions = IncludeAwareWithValueOptions & {
|
|
106
|
+
type?: FooterType
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Enum representing how to group results.
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
export enum GroupResultsBy {
|
|
114
|
+
/**
|
|
115
|
+
* Groups results by the tool name. Particularly, groups by the runs[].tool.driver.name
|
|
116
|
+
* property from the SARIF file(s).
|
|
117
|
+
*/
|
|
118
|
+
ToolName = 0,
|
|
119
|
+
/**
|
|
120
|
+
* Groups results by the run. It provides the result from each run individually.
|
|
121
|
+
*/
|
|
122
|
+
Run = 1,
|
|
123
|
+
/**
|
|
124
|
+
* Does not group results. It provides the result from all the runs from all
|
|
125
|
+
* the provided SARIF files.
|
|
126
|
+
*/
|
|
127
|
+
Total = 2,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Enum representing how to calculate results.
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
export enum CalculateResultsBy {
|
|
135
|
+
/**
|
|
136
|
+
* Calculates results by the security level of the findings: Error, Warning,
|
|
137
|
+
* Note and Unknown. At first, it tries to get the security level from runs[].results[].level
|
|
138
|
+
* property. If it is not defined, it tries to get the security level from the
|
|
139
|
+
* respective rule of each result, using the rules[].properties['problem.severity']
|
|
140
|
+
* property.
|
|
141
|
+
*/
|
|
142
|
+
Level = 0,
|
|
143
|
+
/**
|
|
144
|
+
* Calculates results by the security severity of the findings: Critical, High,
|
|
145
|
+
* Medium, Low, None and Unknown. it tries to get the security severity from the
|
|
146
|
+
* respective rule of each result, using the rules[].properties['security-severity']
|
|
147
|
+
* property. This property contains CVSS score, which is then mapped to the
|
|
148
|
+
* security severity value.
|
|
149
|
+
*/
|
|
150
|
+
Severity = 1,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Options for how to output the results in the Slack message.
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
export type SarifToSlackOutput = {
|
|
158
|
+
groupBy: GroupResultsBy,
|
|
159
|
+
calculateBy: CalculateResultsBy,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Options for logging.
|
|
164
|
+
* @public
|
|
165
|
+
*/
|
|
166
|
+
export type LogOptions = {
|
|
167
|
+
level?: LogLevel,
|
|
168
|
+
/**
|
|
169
|
+
* More details here: https://github.com/fullstack-build/tslog?tab=readme-ov-file#pretty-templates-and-styles-color-settings
|
|
170
|
+
*/
|
|
171
|
+
template?: string,
|
|
172
|
+
colored?: boolean,
|
|
173
|
+
}
|
|
174
|
+
|
|
78
175
|
/**
|
|
79
176
|
* Options for the SarifToSlackService.
|
|
80
177
|
* @public
|
|
@@ -86,9 +183,10 @@ export type SarifToSlackServiceOptions = {
|
|
|
86
183
|
username?: string,
|
|
87
184
|
iconUrl?: string,
|
|
88
185
|
color?: string,
|
|
89
|
-
|
|
90
|
-
header?:
|
|
91
|
-
footer?:
|
|
92
|
-
actor?:
|
|
93
|
-
run?:
|
|
186
|
+
log?: LogOptions,
|
|
187
|
+
header?: IncludeAwareWithValueOptions,
|
|
188
|
+
footer?: FooterOptions,
|
|
189
|
+
actor?: IncludeAwareWithValueOptions,
|
|
190
|
+
run?: IncludeAwareOptions,
|
|
191
|
+
output?: SarifToSlackOutput,
|
|
94
192
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReportingDescriptor, Result, Run } from "sarif";
|
|
2
|
+
|
|
3
|
+
export function findRuleByResult(run: Run, result: Result): ReportingDescriptor | undefined {
|
|
4
|
+
const ruleData: { id?: string, index?: number } = {}
|
|
5
|
+
|
|
6
|
+
if (result.rule) {
|
|
7
|
+
if (result.rule?.index) {
|
|
8
|
+
ruleData.index = result.rule.index
|
|
9
|
+
}
|
|
10
|
+
if (result.rule?.id) {
|
|
11
|
+
ruleData.id = result.rule.id
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!ruleData.index && result.ruleIndex) {
|
|
16
|
+
ruleData.index = result.ruleIndex
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (ruleData.index
|
|
20
|
+
&& run.tool.driver?.rules
|
|
21
|
+
&& ruleData.index < run.tool.driver.rules.length) {
|
|
22
|
+
return run.tool.driver.rules[ruleData.index]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// If failed to find rule by index then try to find by ruleId
|
|
26
|
+
if (result.ruleId && run.tool.driver?.rules) {
|
|
27
|
+
return run.tool.driver.rules.find(
|
|
28
|
+
(r: ReportingDescriptor): boolean => r.id === result.ruleId
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type RuleProperty = 'security-severity' | 'problem.severity'
|
|
36
|
+
|
|
37
|
+
export function tryGetRulePropertyByResult<T>(run: Run, result: Result, propertyName: RuleProperty): T | undefined {
|
|
38
|
+
const rule: ReportingDescriptor | undefined = findRuleByResult(run, result)
|
|
39
|
+
if (rule && rule.properties && propertyName in rule.properties) {
|
|
40
|
+
return rule.properties[propertyName] as T
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Map as ImmutableMap } from 'immutable'
|
|
2
|
+
import {
|
|
3
|
+
SecurityLevel,
|
|
4
|
+
SecurityLevelOrder,
|
|
5
|
+
SecuritySeverity,
|
|
6
|
+
SecuritySeverityOrder
|
|
7
|
+
} from '../model/types'
|
|
8
|
+
|
|
9
|
+
export function sortSecurityLevelMap(map: ImmutableMap<SecurityLevel, number>): ImmutableMap<SecurityLevel, number> {
|
|
10
|
+
return map.sortBy(
|
|
11
|
+
(_: number, level: SecurityLevel): SecurityLevel => level,
|
|
12
|
+
(a: SecurityLevel, b: SecurityLevel): number => SecurityLevelOrder.indexOf(a) - SecurityLevelOrder.indexOf(b),
|
|
13
|
+
).asImmutable()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sortSecuritySeverityMap(map: ImmutableMap<SecuritySeverity, number>): ImmutableMap<SecuritySeverity, number> {
|
|
17
|
+
return map.sortBy(
|
|
18
|
+
(_: number, severity: SecuritySeverity): SecuritySeverity => severity,
|
|
19
|
+
(a: SecuritySeverity, b: SecuritySeverity): number => SecuritySeverityOrder.indexOf(a) - SecuritySeverityOrder.indexOf(b),
|
|
20
|
+
).asImmutable()
|
|
21
|
+
}
|
package/src/version.ts
ADDED