@fabasoad/sarif-to-slack 0.2.2 → 0.2.4
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/security.yml +1 -0
- package/.github/workflows/send-sarif-to-slack.yml +6 -0
- package/api-extractor.json +2 -2
- package/dist/index.cjs +643 -0
- package/dist/model/SarifModelPerRun.js +3 -3
- package/dist/sarif-to-slack.d.ts +278 -278
- package/dist/tsdoc-metadata.json +11 -11
- package/dist/utils/SarifUtils.js +29 -9
- package/dist/version.js +1 -1
- package/jest.config.json +1 -1
- package/package.json +4 -4
- package/scripts/build.sh +11 -0
- package/src/model/SarifModelPerRun.ts +5 -2
- package/src/utils/SarifUtils.ts +35 -10
- package/src/version.ts +1 -1
- package/test-data/sarif/runs-1-extensions-1-results-0.sarif +24 -0
- package/test-data/sarif/runs-1-extensions-1.sarif +79 -0
|
@@ -34,6 +34,8 @@ on: # yamllint disable-line rule:truthy
|
|
|
34
34
|
- "Trivy IaC (Error: 1, Note: 1)"
|
|
35
35
|
- "Wiz Container (Error: 12, Warning: 369, Note: 191)"
|
|
36
36
|
- "Wiz IaC (Warning: 5, Note: 5)"
|
|
37
|
+
- "Runs: 1, Extensions: 1, Results: 0"
|
|
38
|
+
- "Runs: 1, Extensions: 1, Results > 0"
|
|
37
39
|
- "Runs: 1, Tools: 1, Results: 0"
|
|
38
40
|
- "Runs: 2, Tools: 1, Results > 0"
|
|
39
41
|
- "Runs: 2, Tools: 1, Results: 0"
|
|
@@ -174,6 +176,10 @@ jobs:
|
|
|
174
176
|
value="wiz-iac.sarif"
|
|
175
177
|
elif [ "${INPUT_SARIF}" = "Runs: 1, Tools: 1, Results: 0" ]; then
|
|
176
178
|
value="runs-1-tools-1-results-0.sarif"
|
|
179
|
+
elif [ "${INPUT_SARIF}" = "Runs: 1, Extensions: 1, Results: 0" ]; then
|
|
180
|
+
value="runs-1-extensions-1-results-0.sarif"
|
|
181
|
+
elif [ "${INPUT_SARIF}" = "Runs: 1, Extensions: 1, Results > 0" ]; then
|
|
182
|
+
value="runs-1-extensions-1.sarif"
|
|
177
183
|
elif [ "${INPUT_SARIF}" = "Runs: 2, Tools: 1, Results > 0" ]; then
|
|
178
184
|
value="runs-2-tools-1.sarif"
|
|
179
185
|
elif [ "${INPUT_SARIF}" = "Runs: 2, Tools: 1, Results: 0" ]; then
|
package/api-extractor.json
CHANGED
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
*
|
|
77
77
|
* DEFAULT VALUE: "crlf"
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
"newlineKind": "lf",
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Specifies how API Extractor sorts members of an enum when generating the .api.json file. By default, the output
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
*
|
|
86
86
|
* DEFAULT VALUE: "by-name"
|
|
87
87
|
*/
|
|
88
|
-
|
|
88
|
+
"enumMemberOrder": "preserve",
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Set to true when invoking API Extractor's test harness. When `testMode` is true, the `toolVersion` field in the
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CalculateResultsBy: () => CalculateResultsBy,
|
|
34
|
+
FooterType: () => FooterType,
|
|
35
|
+
GroupResultsBy: () => GroupResultsBy,
|
|
36
|
+
LogLevel: () => LogLevel,
|
|
37
|
+
SarifToSlackService: () => SarifToSlackService
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/SarifToSlackService.ts
|
|
42
|
+
var import_fs = require("fs");
|
|
43
|
+
|
|
44
|
+
// src/Logger.ts
|
|
45
|
+
var import_tslog = require("tslog");
|
|
46
|
+
|
|
47
|
+
// src/types.ts
|
|
48
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
49
|
+
LogLevel2[LogLevel2["Silly"] = 0] = "Silly";
|
|
50
|
+
LogLevel2[LogLevel2["Trace"] = 1] = "Trace";
|
|
51
|
+
LogLevel2[LogLevel2["Debug"] = 2] = "Debug";
|
|
52
|
+
LogLevel2[LogLevel2["Info"] = 3] = "Info";
|
|
53
|
+
LogLevel2[LogLevel2["Warning"] = 4] = "Warning";
|
|
54
|
+
LogLevel2[LogLevel2["Error"] = 5] = "Error";
|
|
55
|
+
LogLevel2[LogLevel2["Fatal"] = 6] = "Fatal";
|
|
56
|
+
return LogLevel2;
|
|
57
|
+
})(LogLevel || {});
|
|
58
|
+
var FooterType = /* @__PURE__ */ ((FooterType2) => {
|
|
59
|
+
FooterType2["PlainText"] = "plain_text";
|
|
60
|
+
FooterType2["Markdown"] = "mrkdwn";
|
|
61
|
+
return FooterType2;
|
|
62
|
+
})(FooterType || {});
|
|
63
|
+
var GroupResultsBy = /* @__PURE__ */ ((GroupResultsBy2) => {
|
|
64
|
+
GroupResultsBy2[GroupResultsBy2["ToolName"] = 0] = "ToolName";
|
|
65
|
+
GroupResultsBy2[GroupResultsBy2["Run"] = 1] = "Run";
|
|
66
|
+
GroupResultsBy2[GroupResultsBy2["Total"] = 2] = "Total";
|
|
67
|
+
return GroupResultsBy2;
|
|
68
|
+
})(GroupResultsBy || {});
|
|
69
|
+
var CalculateResultsBy = /* @__PURE__ */ ((CalculateResultsBy2) => {
|
|
70
|
+
CalculateResultsBy2[CalculateResultsBy2["Level"] = 0] = "Level";
|
|
71
|
+
CalculateResultsBy2[CalculateResultsBy2["Severity"] = 1] = "Severity";
|
|
72
|
+
return CalculateResultsBy2;
|
|
73
|
+
})(CalculateResultsBy || {});
|
|
74
|
+
|
|
75
|
+
// src/Logger.ts
|
|
76
|
+
var Logger = class _Logger {
|
|
77
|
+
static DEFAULT_LOG_LEVEL = 3 /* Info */;
|
|
78
|
+
static DEFAULT_LOG_TEMPLATE = "[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ";
|
|
79
|
+
static DEFAULT_LOG_COLORED = true;
|
|
80
|
+
static instance;
|
|
81
|
+
static initialize(opts) {
|
|
82
|
+
if (!_Logger.instance) {
|
|
83
|
+
_Logger.instance = new import_tslog.Logger({
|
|
84
|
+
name: "@fabasoad/sarif-to-slack",
|
|
85
|
+
minLevel: process.env.ACTIONS_STEP_DEBUG === "true" ? 0 /* Silly */ : opts?.level ?? _Logger.DEFAULT_LOG_LEVEL,
|
|
86
|
+
type: "pretty",
|
|
87
|
+
prettyLogTimeZone: "UTC",
|
|
88
|
+
prettyLogTemplate: opts?.template ?? _Logger.DEFAULT_LOG_TEMPLATE,
|
|
89
|
+
stylePrettyLogs: opts?.colored ?? _Logger.DEFAULT_LOG_COLORED
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
static warn(...args) {
|
|
94
|
+
_Logger.instance.warn(...args);
|
|
95
|
+
}
|
|
96
|
+
static info(...args) {
|
|
97
|
+
_Logger.instance.info(...args);
|
|
98
|
+
}
|
|
99
|
+
static debug(...args) {
|
|
100
|
+
_Logger.instance.debug(...args);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/Processors.ts
|
|
105
|
+
var fs = __toESM(require("fs"));
|
|
106
|
+
var path = __toESM(require("path"));
|
|
107
|
+
function processColor(color) {
|
|
108
|
+
switch (color) {
|
|
109
|
+
case "success":
|
|
110
|
+
Logger.info(`Converting "${color}" to #008000`);
|
|
111
|
+
return "#008000";
|
|
112
|
+
case "failure":
|
|
113
|
+
Logger.info(`Converting "${color}" to #ff0000`);
|
|
114
|
+
return "#ff0000";
|
|
115
|
+
case "cancelled":
|
|
116
|
+
Logger.info(`Converting "${color}" to #0047ab`);
|
|
117
|
+
return "#0047ab";
|
|
118
|
+
case "skipped":
|
|
119
|
+
Logger.info(`Converting "${color}" to #808080`);
|
|
120
|
+
return "#808080";
|
|
121
|
+
default:
|
|
122
|
+
Logger.debug(`"${color}" color is not a CI status identifier. Returning as is.`);
|
|
123
|
+
return color;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function processSarifPath(sarifPath) {
|
|
127
|
+
if (!fs.existsSync(sarifPath)) {
|
|
128
|
+
throw new Error(`"sarif-path" does not exist: ${sarifPath}`);
|
|
129
|
+
}
|
|
130
|
+
const sarifStats = fs.statSync(sarifPath);
|
|
131
|
+
if (sarifStats.isDirectory()) {
|
|
132
|
+
Logger.info(`"sarif-path" is a directory: ${sarifPath}`);
|
|
133
|
+
const files = fs.readdirSync(sarifPath);
|
|
134
|
+
const filteredFiles = files.filter(
|
|
135
|
+
(file) => path.extname(file).toLowerCase() === ".sarif"
|
|
136
|
+
);
|
|
137
|
+
Logger.info(`Found ${filteredFiles.length} SARIF files in ${sarifPath} directory`);
|
|
138
|
+
Logger.debug(`Filtered SARIF files: ${filteredFiles.join(", ")}`);
|
|
139
|
+
return filteredFiles.map((file) => path.join(sarifPath, file));
|
|
140
|
+
}
|
|
141
|
+
if (sarifStats.isFile()) {
|
|
142
|
+
Logger.info(`"sarif-path" is a file: ${sarifPath}`);
|
|
143
|
+
return [sarifPath];
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`"sarif-path" is neither a file nor a directory: ${sarifPath}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/SlackMessageBuilder.ts
|
|
149
|
+
var import_webhook = require("@slack/webhook");
|
|
150
|
+
|
|
151
|
+
// src/version.ts
|
|
152
|
+
var LIB_VERSION = "0.2.4";
|
|
153
|
+
|
|
154
|
+
// src/model/SarifModelPerSarif.ts
|
|
155
|
+
var import_immutable2 = require("immutable");
|
|
156
|
+
|
|
157
|
+
// src/utils/SarifUtils.ts
|
|
158
|
+
function findToolComponentByResult(run, result) {
|
|
159
|
+
let tool;
|
|
160
|
+
if (result?.rule?.toolComponent?.index != null) {
|
|
161
|
+
tool = run.tool.extensions?.[result.rule.toolComponent.index];
|
|
162
|
+
}
|
|
163
|
+
if (!tool) {
|
|
164
|
+
tool = run.tool.driver;
|
|
165
|
+
}
|
|
166
|
+
return tool;
|
|
167
|
+
}
|
|
168
|
+
function findRuleByResult(run, result) {
|
|
169
|
+
const ruleData = {};
|
|
170
|
+
if (result.rule) {
|
|
171
|
+
if (result.rule?.index != null) {
|
|
172
|
+
ruleData.index = result.rule.index;
|
|
173
|
+
}
|
|
174
|
+
if (result.rule?.id) {
|
|
175
|
+
ruleData.id = result.rule.id;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (ruleData.index == null && result.ruleIndex != null) {
|
|
179
|
+
ruleData.index = result.ruleIndex;
|
|
180
|
+
}
|
|
181
|
+
if (!ruleData.id && result.ruleId) {
|
|
182
|
+
ruleData.id = result.ruleId;
|
|
183
|
+
}
|
|
184
|
+
const tool = findToolComponentByResult(run, result);
|
|
185
|
+
if (ruleData.index != null && tool?.rules && ruleData.index < tool.rules.length) {
|
|
186
|
+
return tool.rules[ruleData.index];
|
|
187
|
+
}
|
|
188
|
+
if (ruleData.id && tool?.rules) {
|
|
189
|
+
return tool.rules.find(
|
|
190
|
+
(r) => r.id === ruleData.id
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return void 0;
|
|
194
|
+
}
|
|
195
|
+
function tryGetRulePropertyByResult(run, result, propertyName) {
|
|
196
|
+
const rule = findRuleByResult(run, result);
|
|
197
|
+
if (rule && rule.properties && propertyName in rule.properties) {
|
|
198
|
+
return rule.properties[propertyName];
|
|
199
|
+
}
|
|
200
|
+
return void 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/model/types.ts
|
|
204
|
+
var SecuritySeverityOrder = [
|
|
205
|
+
"Critical" /* Critical */,
|
|
206
|
+
"High" /* High */,
|
|
207
|
+
"Medium" /* Medium */,
|
|
208
|
+
"Low" /* Low */,
|
|
209
|
+
"None" /* None */,
|
|
210
|
+
"Unknown" /* Unknown */
|
|
211
|
+
];
|
|
212
|
+
var SecurityLevelOrder = [
|
|
213
|
+
"Error" /* Error */,
|
|
214
|
+
"Warning" /* Warning */,
|
|
215
|
+
"Note" /* Note */,
|
|
216
|
+
"Unknown" /* Unknown */
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
// src/model/SarifModelPerRun.ts
|
|
220
|
+
var import_immutable = require("immutable");
|
|
221
|
+
|
|
222
|
+
// src/utils/SortUtils.ts
|
|
223
|
+
function sortSecurityLevelMap(map) {
|
|
224
|
+
return map.sortBy(
|
|
225
|
+
(_, level) => level,
|
|
226
|
+
(a, b) => SecurityLevelOrder.indexOf(a) - SecurityLevelOrder.indexOf(b)
|
|
227
|
+
).asImmutable();
|
|
228
|
+
}
|
|
229
|
+
function sortSecuritySeverityMap(map) {
|
|
230
|
+
return map.sortBy(
|
|
231
|
+
(_, severity) => severity,
|
|
232
|
+
(a, b) => SecuritySeverityOrder.indexOf(a) - SecuritySeverityOrder.indexOf(b)
|
|
233
|
+
).asImmutable();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/model/SarifModelPerRun.ts
|
|
237
|
+
var SarifModelPerRun = class {
|
|
238
|
+
toolName;
|
|
239
|
+
_securitySeverityMap;
|
|
240
|
+
_securityLevelMap;
|
|
241
|
+
constructor(run) {
|
|
242
|
+
this.toolName = findToolComponentByResult(run, run.results?.[0]).name;
|
|
243
|
+
this._securitySeverityMap = (0, import_immutable.Map)().asMutable();
|
|
244
|
+
this._securityLevelMap = (0, import_immutable.Map)().asMutable();
|
|
245
|
+
this.buildSecuritySeverityMap(run);
|
|
246
|
+
this.buildSecurityLevelMap(run);
|
|
247
|
+
}
|
|
248
|
+
identifySecuritySeverity(score) {
|
|
249
|
+
if (score === void 0) {
|
|
250
|
+
return "Unknown" /* Unknown */;
|
|
251
|
+
}
|
|
252
|
+
if (score >= 9 && score <= 10) {
|
|
253
|
+
return "Critical" /* Critical */;
|
|
254
|
+
}
|
|
255
|
+
if (score >= 7) {
|
|
256
|
+
return "High" /* High */;
|
|
257
|
+
}
|
|
258
|
+
if (score >= 4) {
|
|
259
|
+
return "Medium" /* Medium */;
|
|
260
|
+
}
|
|
261
|
+
if (score >= 0.1) {
|
|
262
|
+
return "Low" /* Low */;
|
|
263
|
+
}
|
|
264
|
+
if (score == 0) {
|
|
265
|
+
return "None" /* None */;
|
|
266
|
+
}
|
|
267
|
+
Logger.warn(`Unsupported "${score}" security severity. Saving as "Unknown".`);
|
|
268
|
+
return "Unknown" /* Unknown */;
|
|
269
|
+
}
|
|
270
|
+
identifySecurityLevel(level) {
|
|
271
|
+
if (level === void 0) {
|
|
272
|
+
return "Unknown" /* Unknown */;
|
|
273
|
+
}
|
|
274
|
+
if (level.toLowerCase() === "error") {
|
|
275
|
+
return "Error" /* Error */;
|
|
276
|
+
}
|
|
277
|
+
if (level.toLowerCase() === "warning") {
|
|
278
|
+
return "Warning" /* Warning */;
|
|
279
|
+
}
|
|
280
|
+
if (level.toLowerCase() === "note") {
|
|
281
|
+
return "Note" /* Note */;
|
|
282
|
+
}
|
|
283
|
+
Logger.warn(`Unsupported ${level} security level. Saving as "Unknown".`);
|
|
284
|
+
return "Unknown" /* Unknown */;
|
|
285
|
+
}
|
|
286
|
+
buildSecuritySeverityMap(run) {
|
|
287
|
+
const results = run.results ?? [];
|
|
288
|
+
for (const result of results) {
|
|
289
|
+
const severity = this.identifySecuritySeverity(
|
|
290
|
+
tryGetRulePropertyByResult(run, result, "security-severity")
|
|
291
|
+
);
|
|
292
|
+
const count = this._securitySeverityMap.get(severity) || 0;
|
|
293
|
+
this._securitySeverityMap.set(severity, count + 1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
tryGetSecurityLevel(run, result) {
|
|
297
|
+
if (result.level) {
|
|
298
|
+
return result.level;
|
|
299
|
+
}
|
|
300
|
+
return tryGetRulePropertyByResult(run, result, "problem.severity");
|
|
301
|
+
}
|
|
302
|
+
buildSecurityLevelMap(run) {
|
|
303
|
+
const results = run.results ?? [];
|
|
304
|
+
for (const result of results) {
|
|
305
|
+
const level = this.identifySecurityLevel(
|
|
306
|
+
this.tryGetSecurityLevel(run, result)
|
|
307
|
+
);
|
|
308
|
+
const count = this._securityLevelMap.get(level) || 0;
|
|
309
|
+
this._securityLevelMap.set(level, count + 1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
get securitySeverityMap() {
|
|
313
|
+
return sortSecuritySeverityMap(this._securitySeverityMap);
|
|
314
|
+
}
|
|
315
|
+
get securityLevelMap() {
|
|
316
|
+
return sortSecurityLevelMap(this._securityLevelMap);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/model/SarifModelPerSarif.ts
|
|
321
|
+
var SarifModelPerSarif = class {
|
|
322
|
+
sarifModelPerRunList;
|
|
323
|
+
constructor(sarif) {
|
|
324
|
+
this.sarifModelPerRunList = new Array();
|
|
325
|
+
this.buildRunsList(sarif);
|
|
326
|
+
}
|
|
327
|
+
buildRunsList(sarif) {
|
|
328
|
+
for (const run of sarif.runs) {
|
|
329
|
+
this.sarifModelPerRunList.push(new SarifModelPerRun(run));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
groupByToolNameWithSecurityLevel() {
|
|
333
|
+
const result = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
335
|
+
if (!result.has(sarifModelPerRun.toolName)) {
|
|
336
|
+
result.set(sarifModelPerRun.toolName, (0, import_immutable2.Map)().asMutable());
|
|
337
|
+
}
|
|
338
|
+
for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
|
|
339
|
+
const count = result.get(sarifModelPerRun.toolName)?.get(k) || 0;
|
|
340
|
+
result.get(sarifModelPerRun.toolName)?.set(k, count + v);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const [k, v] of result) {
|
|
344
|
+
result.set(k, sortSecurityLevelMap(v));
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
groupByRunWithSecurityLevel() {
|
|
349
|
+
const result = new Array();
|
|
350
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
351
|
+
result.push({
|
|
352
|
+
toolName: sarifModelPerRun.toolName,
|
|
353
|
+
data: sarifModelPerRun.securityLevelMap
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
groupByTotalWithSecurityLevel() {
|
|
359
|
+
const result = (0, import_immutable2.Map)().asMutable();
|
|
360
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
361
|
+
for (const [k, v] of sarifModelPerRun.securityLevelMap.entries()) {
|
|
362
|
+
const count = result.get(k) || 0;
|
|
363
|
+
result.set(k, count + v);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return sortSecurityLevelMap(result);
|
|
367
|
+
}
|
|
368
|
+
groupByToolNameWithSecuritySeverity() {
|
|
369
|
+
const result = /* @__PURE__ */ new Map();
|
|
370
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
371
|
+
if (!result.has(sarifModelPerRun.toolName)) {
|
|
372
|
+
result.set(sarifModelPerRun.toolName, (0, import_immutable2.Map)().asMutable());
|
|
373
|
+
}
|
|
374
|
+
for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
|
|
375
|
+
const count = result.get(sarifModelPerRun.toolName)?.get(k) || 0;
|
|
376
|
+
result.get(sarifModelPerRun.toolName)?.set(k, count + v);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const [k, v] of result.entries()) {
|
|
380
|
+
result.set(k, sortSecuritySeverityMap(v));
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
groupByRunWithSecuritySeverity() {
|
|
385
|
+
const result = new Array();
|
|
386
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
387
|
+
result.push({
|
|
388
|
+
toolName: sarifModelPerRun.toolName,
|
|
389
|
+
data: sarifModelPerRun.securitySeverityMap
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
groupByTotalWithSecuritySeverity() {
|
|
395
|
+
const result = (0, import_immutable2.Map)().asMutable();
|
|
396
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
397
|
+
for (const [k, v] of sarifModelPerRun.securitySeverityMap.entries()) {
|
|
398
|
+
const count = result.get(k) || 0;
|
|
399
|
+
result.set(k, count + v);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return sortSecuritySeverityMap(result);
|
|
403
|
+
}
|
|
404
|
+
listToolNames() {
|
|
405
|
+
const toolNames = /* @__PURE__ */ new Set();
|
|
406
|
+
for (const sarifModelPerRun of this.sarifModelPerRunList) {
|
|
407
|
+
toolNames.add(sarifModelPerRun.toolName);
|
|
408
|
+
}
|
|
409
|
+
return toolNames;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/SlackMessageBuilder.ts
|
|
414
|
+
var SlackMessageBuilder = class {
|
|
415
|
+
webhook;
|
|
416
|
+
gitHubServerUrl;
|
|
417
|
+
color;
|
|
418
|
+
sarifModelPerSarif;
|
|
419
|
+
output;
|
|
420
|
+
header;
|
|
421
|
+
footer;
|
|
422
|
+
actor;
|
|
423
|
+
runId;
|
|
424
|
+
sarif;
|
|
425
|
+
constructor(url, opts) {
|
|
426
|
+
this.webhook = new import_webhook.IncomingWebhook(url, {
|
|
427
|
+
username: opts.username || "SARIF results",
|
|
428
|
+
icon_url: opts.iconUrl
|
|
429
|
+
});
|
|
430
|
+
this.gitHubServerUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
|
|
431
|
+
this.color = opts.color;
|
|
432
|
+
this.sarif = opts.sarif;
|
|
433
|
+
this.sarifModelPerSarif = new SarifModelPerSarif(opts.sarif);
|
|
434
|
+
this.output = opts.output || {
|
|
435
|
+
groupBy: 0 /* ToolName */,
|
|
436
|
+
calculateBy: 0 /* Level */
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
withHeader(header) {
|
|
440
|
+
this.header = {
|
|
441
|
+
type: "header",
|
|
442
|
+
text: {
|
|
443
|
+
type: "plain_text",
|
|
444
|
+
text: header || process.env.GITHUB_REPOSITORY || "SARIF results"
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
withActor(actor) {
|
|
449
|
+
this.actor = actor || process.env.GITHUB_ACTOR;
|
|
450
|
+
}
|
|
451
|
+
withRun() {
|
|
452
|
+
this.runId = process.env.GITHUB_RUN_ID;
|
|
453
|
+
}
|
|
454
|
+
withFooter(text, type) {
|
|
455
|
+
const repoName = "fabasoad/sarif-to-slack";
|
|
456
|
+
const element = text ? { type: type || "plain_text" /* PlainText */, text } : { type: "mrkdwn" /* Markdown */, text: `Generated by <${this.gitHubServerUrl}/${repoName}|@${repoName}@${LIB_VERSION}>` };
|
|
457
|
+
this.footer = {
|
|
458
|
+
type: "context",
|
|
459
|
+
elements: [element]
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
async send() {
|
|
463
|
+
const blocks = [];
|
|
464
|
+
if (this.header) {
|
|
465
|
+
blocks.push(this.header);
|
|
466
|
+
}
|
|
467
|
+
blocks.push({
|
|
468
|
+
type: "section",
|
|
469
|
+
text: {
|
|
470
|
+
type: "mrkdwn",
|
|
471
|
+
text: this.buildText()
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
if (this.footer) {
|
|
475
|
+
blocks.push(this.footer);
|
|
476
|
+
}
|
|
477
|
+
const { text } = await this.webhook.send({
|
|
478
|
+
attachments: [{ color: this.color, blocks }]
|
|
479
|
+
});
|
|
480
|
+
return text;
|
|
481
|
+
}
|
|
482
|
+
buildText() {
|
|
483
|
+
const text = [];
|
|
484
|
+
if (this.actor) {
|
|
485
|
+
const actorUrl = `${this.gitHubServerUrl}/${this.actor}`;
|
|
486
|
+
text.push(`_Triggered by <${actorUrl}|${this.actor}>_`);
|
|
487
|
+
}
|
|
488
|
+
text.push(this.composeSummary());
|
|
489
|
+
if (this.runId) {
|
|
490
|
+
let runText = "Job ";
|
|
491
|
+
if (process.env.GITHUB_REPOSITORY) {
|
|
492
|
+
runText += `<${this.gitHubServerUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${this.runId}|#${this.runId}>`;
|
|
493
|
+
} else {
|
|
494
|
+
runText += `#${this.runId}`;
|
|
495
|
+
}
|
|
496
|
+
text.push(runText);
|
|
497
|
+
}
|
|
498
|
+
return text.join("\n\n");
|
|
499
|
+
}
|
|
500
|
+
composeSummaryWith(map, resultProcessor = (result) => result) {
|
|
501
|
+
const stats = new Array();
|
|
502
|
+
for (const [key, count] of map.entries()) {
|
|
503
|
+
stats.push(`*${key}*: ${count}`);
|
|
504
|
+
}
|
|
505
|
+
return resultProcessor(
|
|
506
|
+
stats.length == 0 ? "No issues found" : stats.join(", ")
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
composeSummary() {
|
|
510
|
+
const summaries = new Array();
|
|
511
|
+
switch (this.output.groupBy) {
|
|
512
|
+
case 0 /* ToolName */: {
|
|
513
|
+
const dataGroupedByToolName = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByToolNameWithSecurityLevel() : this.sarifModelPerSarif.groupByToolNameWithSecuritySeverity();
|
|
514
|
+
for (const [toolName, map] of dataGroupedByToolName.entries()) {
|
|
515
|
+
summaries.push(this.composeSummaryWith(
|
|
516
|
+
map,
|
|
517
|
+
(result) => `*${toolName}*
|
|
518
|
+
${result}`
|
|
519
|
+
));
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
case 1 /* Run */: {
|
|
524
|
+
const dataGroupedByRun = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByRunWithSecurityLevel() : this.sarifModelPerSarif.groupByRunWithSecuritySeverity();
|
|
525
|
+
for (let i = 0; i < dataGroupedByRun.length; i++) {
|
|
526
|
+
const { data, toolName } = dataGroupedByRun[i];
|
|
527
|
+
summaries.push(this.composeSummaryWith(
|
|
528
|
+
data,
|
|
529
|
+
(result) => `_[Run ${i + 1}]_: *${toolName}*
|
|
530
|
+
${result}`
|
|
531
|
+
));
|
|
532
|
+
}
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
default: {
|
|
536
|
+
const dataTotal = this.output.calculateBy === 0 /* Level */ ? this.sarifModelPerSarif.groupByTotalWithSecurityLevel() : this.sarifModelPerSarif.groupByTotalWithSecuritySeverity();
|
|
537
|
+
const toolNames = this.sarifModelPerSarif.listToolNames();
|
|
538
|
+
summaries.push(this.composeSummaryWith(
|
|
539
|
+
dataTotal,
|
|
540
|
+
(result) => `*${Array.from(toolNames).join("*, *")}*
|
|
541
|
+
${result}`
|
|
542
|
+
));
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return summaries.join("\n\n");
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// src/SarifToSlackService.ts
|
|
551
|
+
async function initialize(opts) {
|
|
552
|
+
const slackMessages = /* @__PURE__ */ new Map();
|
|
553
|
+
const sarifFiles = processSarifPath(opts.sarifPath);
|
|
554
|
+
if (sarifFiles.length === 0) {
|
|
555
|
+
throw new Error(`No SARIF files found at the provided path: ${opts.sarifPath}`);
|
|
556
|
+
}
|
|
557
|
+
for (const sarifFile of sarifFiles) {
|
|
558
|
+
const jsonString = await import_fs.promises.readFile(sarifFile, "utf8");
|
|
559
|
+
const messageBuilder = new SlackMessageBuilder(opts.webhookUrl, {
|
|
560
|
+
username: opts.username,
|
|
561
|
+
iconUrl: opts.iconUrl,
|
|
562
|
+
color: processColor(opts.color),
|
|
563
|
+
sarif: JSON.parse(jsonString),
|
|
564
|
+
output: opts.output
|
|
565
|
+
});
|
|
566
|
+
if (opts.header?.include) {
|
|
567
|
+
messageBuilder.withHeader(opts.header?.value);
|
|
568
|
+
}
|
|
569
|
+
if (opts.footer?.include) {
|
|
570
|
+
messageBuilder.withFooter(opts.footer?.value, opts.footer?.type);
|
|
571
|
+
}
|
|
572
|
+
if (opts.actor?.include) {
|
|
573
|
+
messageBuilder.withActor(opts.actor?.value);
|
|
574
|
+
}
|
|
575
|
+
if (opts.run?.include) {
|
|
576
|
+
messageBuilder.withRun();
|
|
577
|
+
}
|
|
578
|
+
slackMessages.set(sarifFile, messageBuilder);
|
|
579
|
+
}
|
|
580
|
+
return slackMessages;
|
|
581
|
+
}
|
|
582
|
+
var SarifToSlackService = class _SarifToSlackService {
|
|
583
|
+
_slackMessages;
|
|
584
|
+
constructor() {
|
|
585
|
+
this._slackMessages = /* @__PURE__ */ new Map();
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Gets the Slack messages prepared for each SARIF file.
|
|
589
|
+
* @returns A read-only map where keys are SARIF file paths and values are SlackMessage instances.
|
|
590
|
+
* @public
|
|
591
|
+
*/
|
|
592
|
+
get slackMessages() {
|
|
593
|
+
return this._slackMessages;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Creates an instance of SarifToSlackService.
|
|
597
|
+
* @param opts - Options for the service, including webhook URL, SARIF path, and other configurations.
|
|
598
|
+
* @returns A promise that resolves to an instance of SarifToSlackService.
|
|
599
|
+
* @throws Error if no SARIF files are found at the provided path.
|
|
600
|
+
* @public
|
|
601
|
+
*/
|
|
602
|
+
static async create(opts) {
|
|
603
|
+
Logger.initialize(opts.log);
|
|
604
|
+
const instance = new _SarifToSlackService();
|
|
605
|
+
const map = await initialize(opts);
|
|
606
|
+
map.forEach((val, key) => instance._slackMessages.set(key, val));
|
|
607
|
+
return instance;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Sends all prepared Slack messages.
|
|
611
|
+
* @returns A promise that resolves when all messages have been sent.
|
|
612
|
+
* @throws Error if a Slack message was not prepared for a SARIF path.
|
|
613
|
+
* @public
|
|
614
|
+
*/
|
|
615
|
+
async sendAll() {
|
|
616
|
+
for (const sarifPath of this._slackMessages.keys()) {
|
|
617
|
+
await this.send(sarifPath);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Sends a Slack message for a specific SARIF path.
|
|
622
|
+
* @param sarifPath - The path of the SARIF file for which the message should be sent.
|
|
623
|
+
* @returns A promise that resolves when the message has been sent.
|
|
624
|
+
* @throws Error if a Slack message was not prepared for the given SARIF path.
|
|
625
|
+
* @public
|
|
626
|
+
*/
|
|
627
|
+
async send(sarifPath) {
|
|
628
|
+
const message = this._slackMessages.get(sarifPath);
|
|
629
|
+
if (!message) {
|
|
630
|
+
throw new Error(`Slack message was not prepared for SARIF path: ${sarifPath}.`);
|
|
631
|
+
}
|
|
632
|
+
const text = await message.send();
|
|
633
|
+
Logger.info(`Message sent for ${sarifPath} file. Status:`, text);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
637
|
+
0 && (module.exports = {
|
|
638
|
+
CalculateResultsBy,
|
|
639
|
+
FooterType,
|
|
640
|
+
GroupResultsBy,
|
|
641
|
+
LogLevel,
|
|
642
|
+
SarifToSlackService
|
|
643
|
+
});
|