@fabasoad/sarif-to-slack 0.2.5 → 1.1.0
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/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +0 -1
- package/.github/workflows/send-sarif-to-slack.yml +145 -73
- package/.gitleaksignore +8 -0
- package/.pre-commit-config.yaml +3 -3
- package/.tool-versions +1 -1
- package/dist/Logger.js +4 -1
- package/dist/SarifToSlackClient.d.ts +33 -0
- package/dist/SarifToSlackClient.d.ts.map +1 -0
- package/dist/SarifToSlackClient.js +178 -0
- package/dist/SlackMessageBuilder.js +34 -82
- package/dist/System.d.ts +1 -3
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +10 -3
- package/dist/index.cjs +826 -472
- package/dist/index.d.ts +35 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -12
- package/dist/model/Color.d.ts +80 -0
- package/dist/model/Color.d.ts.map +1 -0
- package/dist/model/Color.js +106 -0
- package/dist/model/Finding.d.ts +2 -0
- package/dist/model/Finding.d.ts.map +1 -0
- package/dist/model/Finding.js +93 -0
- package/dist/model/FindingsArray.d.ts +2 -0
- package/dist/model/FindingsArray.d.ts.map +1 -0
- package/dist/model/FindingsArray.js +24 -0
- package/dist/processors/CodeQLProcessor.d.ts +2 -0
- package/dist/processors/CodeQLProcessor.d.ts.map +1 -0
- package/dist/processors/CodeQLProcessor.js +17 -0
- package/dist/processors/CommonProcessor.d.ts +2 -0
- package/dist/processors/CommonProcessor.d.ts.map +1 -0
- package/dist/processors/CommonProcessor.js +84 -0
- package/dist/processors/ProcessorFactory.d.ts +2 -0
- package/dist/processors/ProcessorFactory.d.ts.map +1 -0
- package/dist/processors/ProcessorFactory.js +22 -0
- package/dist/processors/SnykProcessor.d.ts +2 -0
- package/dist/processors/SnykProcessor.d.ts.map +1 -0
- package/dist/processors/SnykProcessor.js +18 -0
- package/dist/representations/CompactGroupByRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByRepresentation.js +58 -0
- package/dist/representations/CompactGroupByRunPerLevelRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByRunPerLevelRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByRunPerLevelRepresentation.js +13 -0
- package/dist/representations/CompactGroupByRunPerSeverityRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByRunPerSeverityRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByRunPerSeverityRepresentation.js +13 -0
- package/dist/representations/CompactGroupByRunRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByRunRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByRunRepresentation.js +39 -0
- package/dist/representations/CompactGroupBySarifPerLevelRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupBySarifPerLevelRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupBySarifPerLevelRepresentation.js +13 -0
- package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.js +13 -0
- package/dist/representations/CompactGroupBySarifRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupBySarifRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupBySarifRepresentation.js +40 -0
- package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.js +13 -0
- package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.js +13 -0
- package/dist/representations/CompactGroupByToolNameRepresentation.d.ts +2 -0
- package/dist/representations/CompactGroupByToolNameRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactGroupByToolNameRepresentation.js +39 -0
- package/dist/representations/CompactTotalPerLevelRepresentation.d.ts +2 -0
- package/dist/representations/CompactTotalPerLevelRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactTotalPerLevelRepresentation.js +13 -0
- package/dist/representations/CompactTotalPerSeverityRepresentation.d.ts +2 -0
- package/dist/representations/CompactTotalPerSeverityRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactTotalPerSeverityRepresentation.js +13 -0
- package/dist/representations/CompactTotalRepresentation.d.ts +2 -0
- package/dist/representations/CompactTotalRepresentation.d.ts.map +1 -0
- package/dist/representations/CompactTotalRepresentation.js +25 -0
- package/dist/representations/Representation.d.ts +2 -0
- package/dist/representations/Representation.d.ts.map +1 -0
- package/dist/representations/Representation.js +28 -0
- package/dist/representations/RepresentationFactory.d.ts +2 -0
- package/dist/representations/RepresentationFactory.d.ts.map +1 -0
- package/dist/representations/RepresentationFactory.js +37 -0
- package/dist/sarif-to-slack.d.ts +347 -85
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types.d.ts +215 -51
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +225 -33
- package/dist/utils/Comparators.d.ts +2 -0
- package/dist/utils/Comparators.d.ts.map +1 -0
- package/dist/utils/Comparators.js +18 -0
- package/dist/utils/ExtendedArray.d.ts +2 -0
- package/dist/utils/ExtendedArray.d.ts.map +1 -0
- package/dist/utils/ExtendedArray.js +11 -0
- package/dist/utils/FileUtils.d.ts +2 -0
- package/dist/utils/FileUtils.d.ts.map +1 -0
- package/dist/utils/FileUtils.js +51 -0
- package/dist/utils/SarifUtils.js +20 -54
- package/etc/sarif-to-slack.api.md +162 -99
- package/jest.config.json +2 -2
- package/package.json +7 -7
- package/scripts/save-metadata.sh +12 -10
- package/src/Logger.ts +4 -0
- package/src/SarifToSlackClient.ts +202 -0
- package/src/SlackMessageBuilder.ts +35 -115
- package/src/System.ts +9 -2
- package/src/index.ts +47 -20
- package/src/model/Color.ts +195 -0
- package/src/model/Finding.ts +137 -0
- package/src/model/FindingsArray.ts +27 -0
- package/src/processors/CodeQLProcessor.ts +19 -0
- package/src/processors/CommonProcessor.ts +103 -0
- package/src/processors/ProcessorFactory.ts +23 -0
- package/src/processors/SnykProcessor.ts +19 -0
- package/src/representations/CompactGroupByRepresentation.ts +67 -0
- package/src/representations/CompactGroupByRunPerLevelRepresentation.ts +14 -0
- package/src/representations/CompactGroupByRunPerSeverityRepresentation.ts +14 -0
- package/src/representations/CompactGroupByRunRepresentation.ts +44 -0
- package/src/representations/CompactGroupBySarifPerLevelRepresentation.ts +15 -0
- package/src/representations/CompactGroupBySarifPerSeverityRepresentation.ts +15 -0
- package/src/representations/CompactGroupBySarifRepresentation.ts +45 -0
- package/src/representations/CompactGroupByToolNamePerLevelRepresentation.ts +15 -0
- package/src/representations/CompactGroupByToolNamePerSeverityRepresentation.ts +15 -0
- package/src/representations/CompactGroupByToolNameRepresentation.ts +44 -0
- package/src/representations/CompactTotalPerLevelRepresentation.ts +14 -0
- package/src/representations/CompactTotalPerSeverityRepresentation.ts +14 -0
- package/src/representations/CompactTotalRepresentation.ts +27 -0
- package/src/representations/Representation.ts +35 -0
- package/src/representations/RepresentationFactory.ts +49 -0
- package/src/types.ts +270 -53
- package/src/utils/Comparators.ts +19 -0
- package/src/utils/ExtendedArray.ts +11 -0
- package/src/utils/FileUtils.ts +60 -0
- package/src/utils/SarifUtils.ts +20 -72
- package/test-data/sarif/codeql-python.sarif +1448 -1
- package/test-data/sarif/codeql-typescript.sarif +3474 -1
- package/test-data/sarif/grype-github-actions.sarif +65 -0
- package/test-data/sarif/osv-scanner-composer.sarif +972 -0
- package/test-data/sarif/osv-scanner-container.sarif +2278 -0
- package/test-data/sarif/osv-scanner-gomodules.sarif +813 -0
- package/test-data/sarif/osv-scanner-hex.sarif +147 -0
- package/test-data/sarif/osv-scanner-maven.sarif +171 -0
- package/test-data/sarif/osv-scanner-npm.sarif +627 -0
- package/test-data/sarif/osv-scanner-pip.sarif +206 -0
- package/test-data/sarif/osv-scanner-pipenv.sarif +243 -0
- package/test-data/sarif/osv-scanner-pnpm.sarif +174 -0
- package/test-data/sarif/osv-scanner-poetry.sarif +1893 -0
- package/test-data/sarif/osv-scanner-rubygems.sarif +402 -0
- package/test-data/sarif/osv-scanner-uv.sarif +206 -0
- package/test-data/sarif/osv-scanner-yarn.sarif +5207 -0
- package/test-data/sarif/runs-0.sarif +5 -0
- package/test-data/sarif/runs-2-tools-2-results-0.sarif +1 -1
- package/test-data/sarif/runs-2-tools-2.sarif +1 -1
- package/test-data/sarif/runs-3-tools-2-results-0.sarif +1 -1
- package/test-data/sarif/runs-3-tools-2.sarif +1 -1
- package/test-data/sarif/tmp/codeql-csharp.sarif +1 -0
- package/test-data/sarif/tmp/grype-container.sarif +1774 -0
- package/test-data/sarif/tmp/runs-1-tools-1-results-0.sarif +18 -0
- package/test-data/sarif/tmp/runs-2-tools-2.sarif +686 -0
- package/test-data/sarif/trivy-iac.sarif +1 -1
- package/tests/integration/SendSarifToSlack.spec.ts +95 -27
- package/tsconfig.json +2 -0
- package/dist/Processors.d.ts +0 -2
- package/dist/Processors.d.ts.map +0 -1
- package/dist/Processors.js +0 -61
- package/dist/SarifToSlackService.d.ts +0 -39
- package/dist/SarifToSlackService.d.ts.map +0 -1
- package/dist/SarifToSlackService.js +0 -104
- package/dist/metadata.d.ts +0 -2
- package/dist/metadata.d.ts.map +0 -1
- package/dist/metadata.js +0 -11
- package/dist/model/SarifModelPerRun.d.ts +0 -2
- package/dist/model/SarifModelPerRun.d.ts.map +0 -1
- package/dist/model/SarifModelPerRun.js +0 -90
- package/dist/model/SarifModelPerSarif.d.ts +0 -2
- package/dist/model/SarifModelPerSarif.d.ts.map +0 -1
- package/dist/model/SarifModelPerSarif.js +0 -102
- package/dist/model/types.d.ts +0 -2
- package/dist/model/types.d.ts.map +0 -1
- package/dist/model/types.js +0 -49
- package/dist/utils/SortUtils.d.ts +0 -2
- package/dist/utils/SortUtils.d.ts.map +0 -1
- package/dist/utils/SortUtils.js +0 -20
- package/src/Processors.ts +0 -68
- package/src/SarifToSlackService.ts +0 -117
- package/src/metadata.ts +0 -10
- package/src/model/SarifModelPerRun.ts +0 -120
- package/src/model/SarifModelPerSarif.ts +0 -126
- package/src/model/types.ts +0 -50
- package/src/utils/SortUtils.ts +0 -33
- package/tests/Processors.spec.ts +0 -76
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { promises as fs } from 'fs'
|
|
2
|
+
import { Log } from 'sarif'
|
|
3
|
+
import Logger from './Logger'
|
|
4
|
+
import { SlackMessageBuilder } from './SlackMessageBuilder'
|
|
5
|
+
import {
|
|
6
|
+
LogOptions,
|
|
7
|
+
RunData,
|
|
8
|
+
SarifModel,
|
|
9
|
+
SarifOptions,
|
|
10
|
+
SarifToSlackClientOptions,
|
|
11
|
+
SecurityLevel,
|
|
12
|
+
SecuritySeverity,
|
|
13
|
+
SendIf,
|
|
14
|
+
SlackMessage
|
|
15
|
+
} from './types'
|
|
16
|
+
import System from './System'
|
|
17
|
+
import { extractListOfFiles } from './utils/FileUtils'
|
|
18
|
+
import { createRepresentation } from './representations/RepresentationFactory'
|
|
19
|
+
import { createFinding } from './model/Finding'
|
|
20
|
+
import { findToolComponent, findToolComponentDriver } from './utils/SarifUtils'
|
|
21
|
+
import { identifyColor } from './model/Color'
|
|
22
|
+
import FindingsArray from './model/FindingsArray'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Service to convert SARIF files to Slack messages and send them.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export class SarifToSlackClient {
|
|
29
|
+
private _message?: SlackMessage
|
|
30
|
+
private _sarifModel?: SarifModel
|
|
31
|
+
|
|
32
|
+
private _sendIf: SendIf = SendIf.Always
|
|
33
|
+
|
|
34
|
+
private constructor(log?: LogOptions) {
|
|
35
|
+
Logger.initialize(log)
|
|
36
|
+
System.initialize()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static *createRunIdGenerator(): Generator<number> {
|
|
40
|
+
let runId: number = 1
|
|
41
|
+
while (true) {
|
|
42
|
+
yield runId++
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static async create(opts: SarifToSlackClientOptions): Promise<SarifToSlackClient> {
|
|
47
|
+
const instance = new SarifToSlackClient(opts.log)
|
|
48
|
+
Logger.trace('opts', opts)
|
|
49
|
+
instance._sendIf = opts.sendIf ?? instance._sendIf
|
|
50
|
+
instance._sarifModel = await SarifToSlackClient.buildModel(opts.sarif)
|
|
51
|
+
Logger.trace('instance._sarifModel', instance._sarifModel)
|
|
52
|
+
instance._message = await SarifToSlackClient.initialize(instance._sarifModel, opts)
|
|
53
|
+
Logger.trace('instance._message', instance._message)
|
|
54
|
+
return instance;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static async buildModel(sarifOpts: SarifOptions): Promise<SarifModel> {
|
|
58
|
+
const sarifFiles: string[] = extractListOfFiles(sarifOpts)
|
|
59
|
+
if (sarifFiles.length === 0) {
|
|
60
|
+
throw new Error(`No SARIF files found at the provided path: ${sarifOpts.path}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const model: SarifModel = { sarifFiles, runs: [], findings: new FindingsArray() }
|
|
64
|
+
const runIdGenerator: Generator<number> = SarifToSlackClient.createRunIdGenerator()
|
|
65
|
+
for (const sarifPath of sarifFiles) {
|
|
66
|
+
const sarifJson: string = await fs.readFile(sarifPath, 'utf8')
|
|
67
|
+
const sarifLog: Log = JSON.parse(sarifJson) as Log
|
|
68
|
+
|
|
69
|
+
for (const run of sarifLog.runs) {
|
|
70
|
+
const runId: IteratorResult<number> = runIdGenerator.next()
|
|
71
|
+
let runMetadata: RunData | undefined = undefined
|
|
72
|
+
for (const result of run.results ?? []) {
|
|
73
|
+
runMetadata = {
|
|
74
|
+
id: runId.value,
|
|
75
|
+
run,
|
|
76
|
+
toolName: findToolComponent(run, result).name
|
|
77
|
+
}
|
|
78
|
+
model.findings.push(createFinding({ sarifPath, result, runMetadata }))
|
|
79
|
+
}
|
|
80
|
+
runMetadata ??= {
|
|
81
|
+
id: runId.value, run, toolName: findToolComponentDriver(run).name
|
|
82
|
+
}
|
|
83
|
+
model.runs.push(runMetadata)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return model
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The main function to initialize a list of {@link SlackMessage} objects based
|
|
91
|
+
* on the given SARIF file(s).
|
|
92
|
+
* @param sarifModel An instance of {@link SarifModel} object.
|
|
93
|
+
* @param opts An instance of {@link SarifToSlackClientOptions} object.
|
|
94
|
+
* @returns A map where key is the SARIF file and value is an instance of
|
|
95
|
+
* {@link SlackMessage} object
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
private static async initialize(
|
|
99
|
+
sarifModel: SarifModel,
|
|
100
|
+
opts: Omit<SarifToSlackClientOptions, 'sarif' | 'log' | 'sendIf'>
|
|
101
|
+
): Promise<SlackMessage> {
|
|
102
|
+
const message: SlackMessage = new SlackMessageBuilder(opts.webhookUrl, {
|
|
103
|
+
username: opts.username,
|
|
104
|
+
iconUrl: opts.iconUrl,
|
|
105
|
+
color: identifyColor(sarifModel.findings, opts.color),
|
|
106
|
+
representation: createRepresentation(sarifModel, opts.representation),
|
|
107
|
+
})
|
|
108
|
+
if (opts.header?.include) {
|
|
109
|
+
message.withHeader(opts.header?.value)
|
|
110
|
+
}
|
|
111
|
+
if (opts.footer?.include) {
|
|
112
|
+
message.withFooter(opts.footer?.value, opts.footer?.type)
|
|
113
|
+
}
|
|
114
|
+
if (opts.actor?.include) {
|
|
115
|
+
message.withActor(opts.actor?.value)
|
|
116
|
+
}
|
|
117
|
+
if (opts.run?.include) {
|
|
118
|
+
message.withRun()
|
|
119
|
+
}
|
|
120
|
+
return message
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sends a Slack message.
|
|
125
|
+
* @returns A promise that resolves when the message has been sent.
|
|
126
|
+
* @throws Error if a Slack message was not prepared for the given SARIF path.
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
public async send(): Promise<void> {
|
|
130
|
+
if (this._sarifModel == null) {
|
|
131
|
+
throw new Error('Could not parse SARIF file(s).')
|
|
132
|
+
}
|
|
133
|
+
if (this.shouldSendMessage) {
|
|
134
|
+
if (this._message == null) {
|
|
135
|
+
throw new Error('Slack message was not prepared.')
|
|
136
|
+
}
|
|
137
|
+
const text: string = await this._message.send()
|
|
138
|
+
Logger.info('Message sent. Status:', text)
|
|
139
|
+
} else {
|
|
140
|
+
Logger.info('Message was not sent based on the sendIf parameter:', SendIf[this._sendIf])
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private get shouldSendMessage(): boolean {
|
|
145
|
+
if (this._sendIf == null) {
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
switch (this._sendIf) {
|
|
150
|
+
case SendIf.SeverityCritical:
|
|
151
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Critical) != null
|
|
152
|
+
case SendIf.SeverityHigh:
|
|
153
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.High) != null
|
|
154
|
+
case SendIf.SeverityHighOrHigher:
|
|
155
|
+
return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.High)
|
|
156
|
+
case SendIf.SeverityMedium:
|
|
157
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Medium) != null
|
|
158
|
+
case SendIf.SeverityMediumOrHigher:
|
|
159
|
+
return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Medium)
|
|
160
|
+
case SendIf.SeverityLow:
|
|
161
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Low) != null
|
|
162
|
+
case SendIf.SeverityLowOrHigher:
|
|
163
|
+
return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Low)
|
|
164
|
+
case SendIf.SeverityNone:
|
|
165
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.None) != null
|
|
166
|
+
case SendIf.SeverityNoneOrHigher:
|
|
167
|
+
return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.None)
|
|
168
|
+
case SendIf.SeverityUnknown:
|
|
169
|
+
return this._sarifModel?.findings.findByProperty('severity', SecuritySeverity.Unknown) != null
|
|
170
|
+
case SendIf.SeverityUnknownOrHigher:
|
|
171
|
+
return !!this._sarifModel?.findings.hasSeverityOrHigher(SecuritySeverity.Unknown)
|
|
172
|
+
case SendIf.LevelError:
|
|
173
|
+
return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Error) != null
|
|
174
|
+
case SendIf.LevelWarning:
|
|
175
|
+
return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Warning) != null
|
|
176
|
+
case SendIf.LevelWarningOrHigher:
|
|
177
|
+
return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Warning)
|
|
178
|
+
case SendIf.LevelNote:
|
|
179
|
+
return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Note) != null
|
|
180
|
+
case SendIf.LevelNoteOrHigher:
|
|
181
|
+
return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Note)
|
|
182
|
+
case SendIf.LevelNone:
|
|
183
|
+
return this._sarifModel?.findings.findByProperty('level', SecurityLevel.None) != null
|
|
184
|
+
case SendIf.LevelNoneOrHigher:
|
|
185
|
+
return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.None)
|
|
186
|
+
case SendIf.LevelUnknown:
|
|
187
|
+
return this._sarifModel?.findings.findByProperty('level', SecurityLevel.Unknown) != null
|
|
188
|
+
case SendIf.LevelUnknownOrHigher:
|
|
189
|
+
return !!this._sarifModel?.findings.hasLevelOrHigher(SecurityLevel.Unknown)
|
|
190
|
+
case SendIf.Always:
|
|
191
|
+
return true
|
|
192
|
+
case SendIf.Some:
|
|
193
|
+
return (this._sarifModel?.findings.length ?? 0) > 0
|
|
194
|
+
case SendIf.Empty:
|
|
195
|
+
return (this._sarifModel?.findings.length ?? 0) === 0
|
|
196
|
+
case SendIf.Never:
|
|
197
|
+
return false
|
|
198
|
+
default:
|
|
199
|
+
throw new Error(`Unknown sendIf parameter: ${this._sendIf}`)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -2,21 +2,9 @@ import { AnyBlock } from '@slack/types'
|
|
|
2
2
|
import { ContextBlock, HeaderBlock } from '@slack/types/dist/block-kit/blocks'
|
|
3
3
|
import { TextObject } from '@slack/types/dist/block-kit/composition-objects'
|
|
4
4
|
import { IncomingWebhook } from '@slack/webhook'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
FooterType,
|
|
9
|
-
GroupResultsBy,
|
|
10
|
-
SarifLog,
|
|
11
|
-
SarifToSlackOutput,
|
|
12
|
-
SlackMessage
|
|
13
|
-
} from './types'
|
|
14
|
-
import { LIB_VERSION } from './metadata'
|
|
15
|
-
import {
|
|
16
|
-
DataGroupedByRun,
|
|
17
|
-
SarifModelPerSarif
|
|
18
|
-
} from './model/SarifModelPerSarif';
|
|
19
|
-
import { SecurityLevel, SecuritySeverity } from './model/types';
|
|
5
|
+
import { FooterType, SlackMessage } from './types'
|
|
6
|
+
import { version } from './metadata.json'
|
|
7
|
+
import Representation from './representations/Representation'
|
|
20
8
|
|
|
21
9
|
/**
|
|
22
10
|
* Options for the SlackMessageBuilder.
|
|
@@ -26,8 +14,7 @@ export type SlackMessageBuilderOptions = {
|
|
|
26
14
|
username?: string
|
|
27
15
|
iconUrl?: string
|
|
28
16
|
color?: string
|
|
29
|
-
|
|
30
|
-
output?: SarifToSlackOutput,
|
|
17
|
+
representation: Representation,
|
|
31
18
|
}
|
|
32
19
|
|
|
33
20
|
/**
|
|
@@ -35,36 +22,28 @@ export type SlackMessageBuilderOptions = {
|
|
|
35
22
|
* @internal
|
|
36
23
|
*/
|
|
37
24
|
export class SlackMessageBuilder implements SlackMessage {
|
|
38
|
-
private readonly
|
|
39
|
-
private readonly
|
|
40
|
-
private readonly
|
|
41
|
-
private readonly
|
|
42
|
-
private readonly output: SarifToSlackOutput
|
|
43
|
-
private header?: HeaderBlock
|
|
25
|
+
private readonly _webhook: IncomingWebhook
|
|
26
|
+
private readonly _gitHubServerUrl: string
|
|
27
|
+
private readonly _color?: string
|
|
28
|
+
private readonly _representation: Representation
|
|
44
29
|
|
|
45
|
-
private
|
|
46
|
-
private
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
public readonly sarif: SarifLog
|
|
30
|
+
private _header?: HeaderBlock
|
|
31
|
+
private _footer?: ContextBlock
|
|
32
|
+
private _actor?: string
|
|
33
|
+
private _runId?: string
|
|
50
34
|
|
|
51
35
|
constructor(url: string, opts: SlackMessageBuilderOptions) {
|
|
52
|
-
this.
|
|
36
|
+
this._webhook = new IncomingWebhook(url, {
|
|
53
37
|
username: opts.username || 'SARIF results',
|
|
54
38
|
icon_url: opts.iconUrl
|
|
55
39
|
})
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.sarifModelPerSarif = new SarifModelPerSarif(opts.sarif)
|
|
60
|
-
this.output = opts.output || {
|
|
61
|
-
groupBy: GroupResultsBy.ToolName,
|
|
62
|
-
calculateBy: CalculateResultsBy.Level
|
|
63
|
-
}
|
|
40
|
+
this._gitHubServerUrl = process.env.GITHUB_SERVER_URL || 'https://github.com'
|
|
41
|
+
this._color = opts.color
|
|
42
|
+
this._representation = opts.representation
|
|
64
43
|
}
|
|
65
44
|
|
|
66
45
|
withHeader(header?: string): void {
|
|
67
|
-
this.
|
|
46
|
+
this._header = {
|
|
68
47
|
type: 'header',
|
|
69
48
|
text: {
|
|
70
49
|
type: 'plain_text',
|
|
@@ -74,19 +53,19 @@ export class SlackMessageBuilder implements SlackMessage {
|
|
|
74
53
|
}
|
|
75
54
|
|
|
76
55
|
withActor(actor?: string): void {
|
|
77
|
-
this.
|
|
56
|
+
this._actor = actor || process.env.GITHUB_ACTOR
|
|
78
57
|
}
|
|
79
58
|
|
|
80
59
|
withRun(): void {
|
|
81
|
-
this.
|
|
60
|
+
this._runId = process.env.GITHUB_RUN_ID
|
|
82
61
|
}
|
|
83
62
|
|
|
84
63
|
withFooter(text?: string, type?: FooterType): void {
|
|
85
64
|
const repoName = 'fabasoad/sarif-to-slack'
|
|
86
65
|
const element: TextObject = text
|
|
87
66
|
? { type: type || FooterType.PlainText, text }
|
|
88
|
-
: { type: FooterType.Markdown, text: `Generated by <${this.
|
|
89
|
-
this.
|
|
67
|
+
: { type: FooterType.Markdown, text: `Generated by <${this._gitHubServerUrl}/${repoName}|@${repoName}@${version}>` }
|
|
68
|
+
this._footer = {
|
|
90
69
|
type: 'context',
|
|
91
70
|
elements: [element],
|
|
92
71
|
}
|
|
@@ -94,100 +73,41 @@ export class SlackMessageBuilder implements SlackMessage {
|
|
|
94
73
|
|
|
95
74
|
async send(): Promise<string> {
|
|
96
75
|
const blocks: AnyBlock[] = []
|
|
97
|
-
if (this.
|
|
98
|
-
blocks.push(this.
|
|
76
|
+
if (this._header) {
|
|
77
|
+
blocks.push(this._header)
|
|
99
78
|
}
|
|
100
79
|
blocks.push({
|
|
101
80
|
type: 'section',
|
|
102
81
|
text: {
|
|
103
82
|
type: 'mrkdwn',
|
|
104
|
-
text: this.buildText()
|
|
83
|
+
text: this.buildText(),
|
|
105
84
|
}
|
|
106
85
|
})
|
|
107
|
-
if (this.
|
|
108
|
-
blocks.push(this.
|
|
86
|
+
if (this._footer) {
|
|
87
|
+
blocks.push(this._footer)
|
|
109
88
|
}
|
|
110
|
-
const { text } = await this.
|
|
111
|
-
attachments: [{ color: this.
|
|
89
|
+
const { text } = await this._webhook.send({
|
|
90
|
+
attachments: [{ color: this._color, blocks }]
|
|
112
91
|
})
|
|
113
92
|
return text
|
|
114
93
|
}
|
|
115
94
|
|
|
116
95
|
private buildText(): string {
|
|
117
96
|
const text: string[] = []
|
|
118
|
-
if (this.
|
|
119
|
-
const actorUrl = `${this.
|
|
120
|
-
text.push(`_Triggered by <${actorUrl}|${this.
|
|
97
|
+
if (this._actor) {
|
|
98
|
+
const actorUrl = `${this._gitHubServerUrl}/${this._actor}`
|
|
99
|
+
text.push(`_Triggered by <${actorUrl}|${this._actor}>_`)
|
|
121
100
|
}
|
|
122
|
-
text.push(this.
|
|
123
|
-
if (this.
|
|
101
|
+
text.push(this._representation.compose())
|
|
102
|
+
if (this._runId) {
|
|
124
103
|
let runText: string = 'Job '
|
|
125
104
|
if (process.env.GITHUB_REPOSITORY) {
|
|
126
|
-
runText += `<${this.
|
|
105
|
+
runText += `<${this._gitHubServerUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${this._runId}|#${this._runId}>`
|
|
127
106
|
} else {
|
|
128
|
-
runText += `#${this.
|
|
107
|
+
runText += `#${this._runId}`
|
|
129
108
|
}
|
|
130
109
|
text.push(runText)
|
|
131
110
|
}
|
|
132
111
|
return text.join('\n\n')
|
|
133
112
|
}
|
|
134
|
-
|
|
135
|
-
private composeSummaryWith(
|
|
136
|
-
map: ImmutableMap<SecurityLevel | SecuritySeverity, number>,
|
|
137
|
-
resultProcessor: (result: string) => string = (result: string): string => result,
|
|
138
|
-
): string {
|
|
139
|
-
const stats = new Array<string>()
|
|
140
|
-
for (const [key, count] of map.entries()) {
|
|
141
|
-
stats.push(`*${key}*: ${count}`)
|
|
142
|
-
}
|
|
143
|
-
return resultProcessor(
|
|
144
|
-
stats.length == 0 ? 'No issues found' : stats.join(', ')
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private composeSummary(): string {
|
|
149
|
-
const summaries = new Array<string>()
|
|
150
|
-
switch (this.output.groupBy) {
|
|
151
|
-
case GroupResultsBy.ToolName: {
|
|
152
|
-
const dataGroupedByToolName: Map<string, ImmutableMap<SecurityLevel | SecuritySeverity, number>> =
|
|
153
|
-
this.output.calculateBy === CalculateResultsBy.Level
|
|
154
|
-
? this.sarifModelPerSarif.groupByToolNameWithSecurityLevel()
|
|
155
|
-
: this.sarifModelPerSarif.groupByToolNameWithSecuritySeverity()
|
|
156
|
-
for (const [toolName, map] of dataGroupedByToolName.entries()) {
|
|
157
|
-
summaries.push(this.composeSummaryWith(
|
|
158
|
-
map,
|
|
159
|
-
(result: string): string => `*${toolName}*\n${result}`
|
|
160
|
-
))
|
|
161
|
-
}
|
|
162
|
-
break
|
|
163
|
-
}
|
|
164
|
-
case GroupResultsBy.Run: {
|
|
165
|
-
const dataGroupedByRun: Array<DataGroupedByRun<SecurityLevel | SecuritySeverity>> =
|
|
166
|
-
this.output.calculateBy === CalculateResultsBy.Level
|
|
167
|
-
? this.sarifModelPerSarif.groupByRunWithSecurityLevel()
|
|
168
|
-
: this.sarifModelPerSarif.groupByRunWithSecuritySeverity()
|
|
169
|
-
for (let i = 0; i < dataGroupedByRun.length; i++) {
|
|
170
|
-
const { data, toolName } = dataGroupedByRun[i]
|
|
171
|
-
summaries.push(this.composeSummaryWith(
|
|
172
|
-
data,
|
|
173
|
-
(result: string): string => `_[Run ${i + 1}]_: *${toolName}*\n${result}`
|
|
174
|
-
))
|
|
175
|
-
}
|
|
176
|
-
break
|
|
177
|
-
}
|
|
178
|
-
default: {
|
|
179
|
-
const dataTotal: ImmutableMap<SecurityLevel | SecuritySeverity, number> =
|
|
180
|
-
this.output.calculateBy === CalculateResultsBy.Level
|
|
181
|
-
? this.sarifModelPerSarif.groupByTotalWithSecurityLevel()
|
|
182
|
-
: this.sarifModelPerSarif.groupByTotalWithSecuritySeverity()
|
|
183
|
-
const toolNames: Set<string> = this.sarifModelPerSarif.listToolNames()
|
|
184
|
-
summaries.push(this.composeSummaryWith(
|
|
185
|
-
dataTotal,
|
|
186
|
-
(result: string): string => `*${Array.from(toolNames).join('*, *')}*\n${result}`
|
|
187
|
-
))
|
|
188
|
-
break
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return summaries.join('\n\n')
|
|
192
|
-
}
|
|
193
113
|
}
|
package/src/System.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { version, sha, buildAt } from './metadata.json'
|
|
2
2
|
import Logger from './Logger'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* This class prints metadata information into the logs, such as library version,
|
|
6
|
+
* SHA and build time.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
4
9
|
export default class System {
|
|
5
10
|
|
|
6
11
|
public static initialize(): void {
|
|
7
|
-
Logger.info(
|
|
12
|
+
Logger.info(`@fabasoad/sarif-to-slack version: ${version}`)
|
|
13
|
+
Logger.info(`@fabasoad/sarif-to-slack sha: ${sha}`)
|
|
14
|
+
Logger.info(`@fabasoad/sarif-to-slack built at: ${buildAt}`)
|
|
8
15
|
}
|
|
9
16
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,24 +4,45 @@
|
|
|
4
4
|
* Sarif to Slack message converter library.
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
|
-
* This library provides a
|
|
7
|
+
* This library provides a client to send a Slack messages based on the provided
|
|
8
8
|
* SARIF (Static Analysis Results Interchange Format) files.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```typescript
|
|
12
|
-
* import {
|
|
12
|
+
* import {
|
|
13
|
+
* Color,
|
|
14
|
+
* FooterType,
|
|
15
|
+
* LogLevel,
|
|
16
|
+
* RepresentationType,
|
|
17
|
+
* SarifToSlackClient,
|
|
18
|
+
* SendIf
|
|
19
|
+
* } from '@fabasoad/sarif-to-slack';
|
|
13
20
|
*
|
|
14
|
-
* const
|
|
21
|
+
* const client: SarifToSlackClient = await SarifToSlackClient.create({
|
|
15
22
|
* webhookUrl: 'https://hooks.slack.com/services/your/webhook/url',
|
|
16
|
-
*
|
|
23
|
+
* username: 'SARIF to Slack Bot',
|
|
24
|
+
* iconUrl: 'https://example.com/icon.png',
|
|
25
|
+
* color: {
|
|
26
|
+
* bySeverity: {
|
|
27
|
+
* critical: new Color('#ff0000'),
|
|
28
|
+
* high: new Color('#ff4500'),
|
|
29
|
+
* medium: new Color('#ffa500'),
|
|
30
|
+
* low: new Color('#ffff00'),
|
|
31
|
+
* none: new Color('#808080'),
|
|
32
|
+
* unknown: new Color('#800080'),
|
|
33
|
+
* empty: new Color('#d3d3d3'),
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* sarif: {
|
|
37
|
+
* path: 'path/to/your/sarif-files',
|
|
38
|
+
* recursive: true,
|
|
39
|
+
* extension: 'sarif',
|
|
40
|
+
* },
|
|
17
41
|
* log: {
|
|
18
42
|
* level: LogLevel.Info,
|
|
19
43
|
* template: '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ',
|
|
20
44
|
* colored: false,
|
|
21
45
|
* },
|
|
22
|
-
* username: 'SARIF Bot',
|
|
23
|
-
* iconUrl: 'https://example.com/icon.png',
|
|
24
|
-
* color: '#36a64f',
|
|
25
46
|
* header: {
|
|
26
47
|
* include: true,
|
|
27
48
|
* value: 'SARIF Analysis Results'
|
|
@@ -38,28 +59,34 @@
|
|
|
38
59
|
* run: {
|
|
39
60
|
* include: true
|
|
40
61
|
* },
|
|
62
|
+
* representation: RepresentationType.CompactGroupByToolNamePerSeverity,
|
|
63
|
+
* sendIf: SendIf.MediumOrHigher,
|
|
41
64
|
* });
|
|
42
|
-
* await
|
|
65
|
+
* await client.send();
|
|
43
66
|
* ```
|
|
44
67
|
*
|
|
45
|
-
* @see {@link
|
|
68
|
+
* @see {@link SarifToSlackClient}
|
|
46
69
|
*
|
|
47
70
|
* @packageDocumentation
|
|
48
71
|
*/
|
|
49
|
-
export { SarifToSlackService } from './SarifToSlackService'
|
|
50
72
|
export {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} from './
|
|
57
|
-
export
|
|
73
|
+
Color,
|
|
74
|
+
ColorOptions,
|
|
75
|
+
ColorGroupByLevel,
|
|
76
|
+
ColorGroupBySeverity
|
|
77
|
+
} from './model/Color'
|
|
78
|
+
export { SarifToSlackClient } from './SarifToSlackClient'
|
|
79
|
+
export {
|
|
58
80
|
FooterOptions,
|
|
81
|
+
FooterType,
|
|
59
82
|
IncludeAwareOptions,
|
|
60
83
|
IncludeAwareWithValueOptions,
|
|
84
|
+
LogLevel,
|
|
61
85
|
LogOptions,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
RepresentationType,
|
|
87
|
+
SarifFileExtension,
|
|
88
|
+
SarifOptions,
|
|
89
|
+
SarifToSlackClientOptions,
|
|
90
|
+
SendIf,
|
|
91
|
+
SlackMessage,
|
|
65
92
|
} from './types'
|