@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
|
@@ -7,12 +7,36 @@
|
|
|
7
7
|
import type { Log } from 'sarif';
|
|
8
8
|
|
|
9
9
|
// @public
|
|
10
|
-
export
|
|
10
|
+
export enum CalculateResultsBy {
|
|
11
|
+
Level = 0,
|
|
12
|
+
Severity = 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// @public
|
|
16
|
+
export type FooterOptions = IncludeAwareWithValueOptions & {
|
|
17
|
+
type?: FooterType;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// @public
|
|
21
|
+
export enum FooterType {
|
|
22
|
+
Markdown = "mrkdwn",
|
|
23
|
+
PlainText = "plain_text"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// @public
|
|
27
|
+
export enum GroupResultsBy {
|
|
28
|
+
Run = 1,
|
|
29
|
+
ToolName = 0,
|
|
30
|
+
Total = 2
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// @public
|
|
34
|
+
export type IncludeAwareOptions = {
|
|
11
35
|
include: boolean;
|
|
12
36
|
};
|
|
13
37
|
|
|
14
38
|
// @public
|
|
15
|
-
export type
|
|
39
|
+
export type IncludeAwareWithValueOptions = IncludeAwareOptions & {
|
|
16
40
|
value?: string;
|
|
17
41
|
};
|
|
18
42
|
|
|
@@ -28,7 +52,20 @@ export enum LogLevel {
|
|
|
28
52
|
}
|
|
29
53
|
|
|
30
54
|
// @public
|
|
31
|
-
export type
|
|
55
|
+
export type LogOptions = {
|
|
56
|
+
level?: LogLevel;
|
|
57
|
+
template?: string;
|
|
58
|
+
colored?: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// @public
|
|
62
|
+
export type SarifLog = Log;
|
|
63
|
+
|
|
64
|
+
// @public
|
|
65
|
+
export type SarifToSlackOutput = {
|
|
66
|
+
groupBy: GroupResultsBy;
|
|
67
|
+
calculateBy: CalculateResultsBy;
|
|
68
|
+
};
|
|
32
69
|
|
|
33
70
|
// @public
|
|
34
71
|
export class SarifToSlackService {
|
|
@@ -45,16 +82,17 @@ export type SarifToSlackServiceOptions = {
|
|
|
45
82
|
username?: string;
|
|
46
83
|
iconUrl?: string;
|
|
47
84
|
color?: string;
|
|
48
|
-
|
|
49
|
-
header?:
|
|
50
|
-
footer?:
|
|
51
|
-
actor?:
|
|
52
|
-
run?:
|
|
85
|
+
log?: LogOptions;
|
|
86
|
+
header?: IncludeAwareWithValueOptions;
|
|
87
|
+
footer?: FooterOptions;
|
|
88
|
+
actor?: IncludeAwareWithValueOptions;
|
|
89
|
+
run?: IncludeAwareOptions;
|
|
90
|
+
output?: SarifToSlackOutput;
|
|
53
91
|
};
|
|
54
92
|
|
|
55
93
|
// @public
|
|
56
94
|
export interface SlackMessage {
|
|
57
|
-
sarif:
|
|
95
|
+
sarif: SarifLog;
|
|
58
96
|
send: () => Promise<string>;
|
|
59
97
|
}
|
|
60
98
|
|
package/jest.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabasoad/sarif-to-slack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "TypeScript library to send results of SARIF file to Slack webhook URL.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
"private": false,
|
|
9
9
|
"scripts": {
|
|
10
10
|
"lint": "biome lint --write src",
|
|
11
|
-
"test": "jest --config=jest.config.json --json --outputFile=jest-report.json --coverage",
|
|
11
|
+
"test": "jest --config=jest.config.json --json --outputFile=jest-report.json --coverage --testNamePattern=unit",
|
|
12
|
+
"test:integration": "jest --config=jest.config.json --testNamePattern=integration",
|
|
12
13
|
"clean": "rm -rf coverage && rm -rf temp",
|
|
13
14
|
"clean:unsafe": "rm -f package-lock.json && rm -rf node_modules && rm -rf dist && rm -rf lib",
|
|
14
15
|
"tsc": "tsc",
|
|
15
|
-
"
|
|
16
|
+
"prebuild": "./scripts/save-version.sh",
|
|
17
|
+
"build": "npm run tsc && api-extractor run --local --verbose",
|
|
16
18
|
"prepublishOnly": "npm run build",
|
|
19
|
+
"preinstall": "./scripts/save-version.sh",
|
|
17
20
|
"version:patch": "npm version patch --commit-hooks --git-tag-version --message 'chore: bump to version %s'",
|
|
18
21
|
"version:minor": "npm version minor --commit-hooks --git-tag-version --message 'chore: bump to version %s'",
|
|
19
22
|
"version:major": "npm version major --commit-hooks --git-tag-version --message 'chore: bump to version %s'",
|
|
@@ -33,24 +36,25 @@
|
|
|
33
36
|
"author": "Yevhen Fabizhevskyi",
|
|
34
37
|
"license": "MIT",
|
|
35
38
|
"bugs": {
|
|
36
|
-
"url": "https://github.com/fabasoad/sarif-to-slack
|
|
39
|
+
"url": "https://github.com/fabasoad/sarif-to-slack/issues"
|
|
37
40
|
},
|
|
38
41
|
"publishConfig": {
|
|
39
42
|
"access": "public"
|
|
40
43
|
},
|
|
41
|
-
"homepage": "https://github.com/fabasoad/sarif-to-slack
|
|
44
|
+
"homepage": "https://github.com/fabasoad/sarif-to-slack#readme",
|
|
42
45
|
"dependencies": {
|
|
43
46
|
"@slack/webhook": "7.0.5",
|
|
44
47
|
"@types/sarif": "2.1.7",
|
|
48
|
+
"immutable": "5.1.3",
|
|
45
49
|
"tslog": "4.9.3"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
|
-
"@biomejs/biome": "1.
|
|
49
|
-
"@microsoft/api-documenter": "7.26.
|
|
50
|
-
"@microsoft/api-extractor": "7.52.
|
|
52
|
+
"@biomejs/biome": "2.1.2",
|
|
53
|
+
"@microsoft/api-documenter": "7.26.30",
|
|
54
|
+
"@microsoft/api-extractor": "7.52.9",
|
|
51
55
|
"@types/jest": "30.0.0",
|
|
52
|
-
"jest": "30.0.
|
|
53
|
-
"jest-circus": "30.0.
|
|
56
|
+
"jest": "30.0.5",
|
|
57
|
+
"jest-circus": "30.0.5",
|
|
54
58
|
"ts-jest": "29.4.0",
|
|
55
59
|
"typescript": "5.8.3"
|
|
56
60
|
}
|
package/src/Logger.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
import { Logger as TSLogger
|
|
2
|
-
import { LogLevel } from './types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Logger options for configuring the logging behavior.
|
|
6
|
-
* @internal
|
|
7
|
-
*/
|
|
8
|
-
export type LoggerOptions = {
|
|
9
|
-
logLevel?: LogLevel
|
|
10
|
-
}
|
|
1
|
+
import { ILogObj, Logger as TSLogger } from 'tslog'
|
|
2
|
+
import { LogLevel, LogOptions } from './types'
|
|
11
3
|
|
|
12
4
|
/**
|
|
13
5
|
* Logger class for managing logging operations.
|
|
14
6
|
* @internal
|
|
15
7
|
*/
|
|
16
8
|
export default class Logger {
|
|
17
|
-
private static
|
|
9
|
+
private static DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info
|
|
10
|
+
private static DEFAULT_LOG_TEMPLATE: string = '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} '
|
|
11
|
+
private static DEFAULT_LOG_COLORED: boolean = true
|
|
12
|
+
|
|
13
|
+
private static instance: TSLogger<ILogObj>
|
|
18
14
|
|
|
19
|
-
public static initialize(
|
|
15
|
+
public static initialize(opts?: LogOptions): void {
|
|
20
16
|
if (!Logger.instance) {
|
|
21
17
|
Logger.instance = new TSLogger({
|
|
22
|
-
|
|
18
|
+
name: '@fabasoad/sarif-to-slack',
|
|
19
|
+
minLevel: process.env.ACTIONS_STEP_DEBUG === 'true' ? LogLevel.Silly : (opts?.level ?? Logger.DEFAULT_LOG_LEVEL),
|
|
20
|
+
type: 'pretty',
|
|
21
|
+
prettyLogTimeZone: 'UTC',
|
|
22
|
+
prettyLogTemplate: opts?.template ?? Logger.DEFAULT_LOG_TEMPLATE,
|
|
23
|
+
stylePrettyLogs: opts?.colored ?? Logger.DEFAULT_LOG_COLORED,
|
|
23
24
|
})
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
public static
|
|
28
|
-
Logger.instance.
|
|
28
|
+
public static warn(...args: unknown[]): void {
|
|
29
|
+
Logger.instance.warn(...args)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static info(...args: unknown[]): void {
|
|
33
|
+
Logger.instance.info(...args)
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
public static debug(...args:
|
|
32
|
-
Logger.instance.debug(args)
|
|
36
|
+
public static debug(...args: unknown[]): void {
|
|
37
|
+
Logger.instance.debug(...args)
|
|
33
38
|
}
|
|
34
39
|
}
|
package/src/Processors.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function processColor(color?: string): string | undefined {
|
|
|
27
27
|
Logger.info(`Converting "${color}" to #808080`)
|
|
28
28
|
return '#808080'
|
|
29
29
|
default:
|
|
30
|
-
Logger.debug(`"${color}" color is not a CI status identifier. Returning as is
|
|
30
|
+
Logger.debug(`"${color}" color is not a CI status identifier. Returning as is.`)
|
|
31
31
|
return color
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -39,28 +39,28 @@ export function processColor(color?: string): string | undefined {
|
|
|
39
39
|
* @throws Error If the input string does not match any known log level.
|
|
40
40
|
* @internal
|
|
41
41
|
*/
|
|
42
|
-
export function processLogLevel(logLevel?:
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
export function processLogLevel(logLevel?: string): LogLevel | undefined {
|
|
43
|
+
if (!logLevel) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
switch (logLevel.toLowerCase()) {
|
|
47
|
+
case 'silly':
|
|
48
|
+
return LogLevel.Silly
|
|
49
|
+
case 'trace':
|
|
50
|
+
return LogLevel.Trace
|
|
51
|
+
case 'debug':
|
|
52
|
+
return LogLevel.Debug
|
|
53
|
+
case 'info':
|
|
54
|
+
return LogLevel.Info
|
|
55
|
+
case 'warning':
|
|
56
|
+
return LogLevel.Warning
|
|
57
|
+
case 'error':
|
|
58
|
+
return LogLevel.Error
|
|
59
|
+
case 'fatal':
|
|
60
|
+
return LogLevel.Fatal
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`Unknown log level: ${logLevel}`)
|
|
62
63
|
}
|
|
63
|
-
return logLevel
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import Logger from './Logger'
|
|
3
|
-
import { processColor,
|
|
3
|
+
import { processColor, processSarifPath } from './Processors'
|
|
4
4
|
import { SlackMessageBuilder } from './SlackMessageBuilder'
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
SarifLog,
|
|
7
7
|
SarifToSlackServiceOptions,
|
|
8
8
|
SlackMessage
|
|
9
9
|
} from './types'
|
|
@@ -22,13 +22,14 @@ async function initialize(opts: SarifToSlackServiceOptions): Promise<Map<string,
|
|
|
22
22
|
username: opts.username,
|
|
23
23
|
iconUrl: opts.iconUrl,
|
|
24
24
|
color: processColor(opts.color),
|
|
25
|
-
sarif: JSON.parse(jsonString) as
|
|
25
|
+
sarif: JSON.parse(jsonString) as SarifLog,
|
|
26
|
+
output: opts.output,
|
|
26
27
|
})
|
|
27
28
|
if (opts.header?.include) {
|
|
28
29
|
messageBuilder.withHeader(opts.header?.value)
|
|
29
30
|
}
|
|
30
31
|
if (opts.footer?.include) {
|
|
31
|
-
messageBuilder.withFooter(opts.footer?.value)
|
|
32
|
+
messageBuilder.withFooter(opts.footer?.value, opts.footer?.type)
|
|
32
33
|
}
|
|
33
34
|
if (opts.actor?.include) {
|
|
34
35
|
messageBuilder.withActor(opts.actor?.value)
|
|
@@ -69,9 +70,7 @@ export class SarifToSlackService {
|
|
|
69
70
|
* @public
|
|
70
71
|
*/
|
|
71
72
|
public static async create(opts: SarifToSlackServiceOptions): Promise<SarifToSlackService> {
|
|
72
|
-
Logger.initialize(
|
|
73
|
-
logLevel: processLogLevel(opts.logLevel)
|
|
74
|
-
})
|
|
73
|
+
Logger.initialize(opts.log)
|
|
75
74
|
const instance: SarifToSlackService = new SarifToSlackService()
|
|
76
75
|
const map: Map<string, SlackMessage> = await initialize(opts)
|
|
77
76
|
map.forEach((val: SlackMessage, key: string) => instance._slackMessages.set(key, val))
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { AnyBlock } from '@slack/types'
|
|
2
|
-
import {
|
|
2
|
+
import { ContextBlock, HeaderBlock } from '@slack/types/dist/block-kit/blocks'
|
|
3
|
+
import { TextObject } from '@slack/types/dist/block-kit/composition-objects'
|
|
3
4
|
import { IncomingWebhook } from '@slack/webhook'
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
5
|
+
import { Map as ImmutableMap } from 'immutable'
|
|
6
|
+
import {
|
|
7
|
+
CalculateResultsBy,
|
|
8
|
+
FooterType,
|
|
9
|
+
GroupResultsBy,
|
|
10
|
+
SarifLog,
|
|
11
|
+
SarifToSlackOutput,
|
|
12
|
+
SlackMessage
|
|
13
|
+
} from './types'
|
|
14
|
+
import { LIB_VERSION } from './version'
|
|
15
|
+
import {
|
|
16
|
+
DataGroupedByRun,
|
|
17
|
+
SarifModelPerSarif
|
|
18
|
+
} from './model/SarifModelPerSarif';
|
|
19
|
+
import { SecurityLevel, SecuritySeverity } from './model/types';
|
|
6
20
|
|
|
7
21
|
/**
|
|
8
22
|
* Options for the SlackMessageBuilder.
|
|
@@ -12,11 +26,10 @@ export type SlackMessageBuilderOptions = {
|
|
|
12
26
|
username?: string
|
|
13
27
|
iconUrl?: string
|
|
14
28
|
color?: string
|
|
15
|
-
sarif:
|
|
29
|
+
sarif: SarifLog,
|
|
30
|
+
output?: SarifToSlackOutput,
|
|
16
31
|
}
|
|
17
32
|
|
|
18
|
-
type RuleData = { id?: string, index?: number }
|
|
19
|
-
|
|
20
33
|
/**
|
|
21
34
|
* Class for building and sending Slack messages based on SARIF logs.
|
|
22
35
|
* @internal
|
|
@@ -25,22 +38,29 @@ export class SlackMessageBuilder implements SlackMessage {
|
|
|
25
38
|
private readonly webhook: IncomingWebhook
|
|
26
39
|
private readonly gitHubServerUrl: string
|
|
27
40
|
private readonly color?: string
|
|
28
|
-
|
|
41
|
+
private readonly sarifModelPerSarif: SarifModelPerSarif
|
|
42
|
+
private readonly output: SarifToSlackOutput
|
|
29
43
|
private header?: HeaderBlock
|
|
44
|
+
|
|
30
45
|
private footer?: ContextBlock
|
|
31
46
|
private actor?: string
|
|
32
47
|
private runId?: string
|
|
33
48
|
|
|
34
|
-
public readonly sarif:
|
|
49
|
+
public readonly sarif: SarifLog
|
|
35
50
|
|
|
36
51
|
constructor(url: string, opts: SlackMessageBuilderOptions) {
|
|
37
52
|
this.webhook = new IncomingWebhook(url, {
|
|
38
53
|
username: opts.username || 'SARIF results',
|
|
39
54
|
icon_url: opts.iconUrl
|
|
40
55
|
})
|
|
56
|
+
this.gitHubServerUrl = process.env.GITHUB_SERVER_URL || 'https://github.com'
|
|
41
57
|
this.color = opts.color
|
|
42
58
|
this.sarif = opts.sarif
|
|
43
|
-
this.
|
|
59
|
+
this.sarifModelPerSarif = new SarifModelPerSarif(opts.sarif)
|
|
60
|
+
this.output = opts.output || {
|
|
61
|
+
groupBy: GroupResultsBy.ToolName,
|
|
62
|
+
calculateBy: CalculateResultsBy.Level
|
|
63
|
+
}
|
|
44
64
|
}
|
|
45
65
|
|
|
46
66
|
withHeader(header?: string): void {
|
|
@@ -61,14 +81,14 @@ export class SlackMessageBuilder implements SlackMessage {
|
|
|
61
81
|
this.runId = process.env.GITHUB_RUN_ID
|
|
62
82
|
}
|
|
63
83
|
|
|
64
|
-
withFooter(
|
|
65
|
-
const repoName = 'fabasoad/sarif-to-slack
|
|
84
|
+
withFooter(text?: string, type?: FooterType): void {
|
|
85
|
+
const repoName = 'fabasoad/sarif-to-slack'
|
|
86
|
+
const element: TextObject = text
|
|
87
|
+
? { type: type || FooterType.PlainText, text }
|
|
88
|
+
: { type: FooterType.Markdown, text: `Generated by <${this.gitHubServerUrl}/${repoName}|@${repoName}@${LIB_VERSION}>` }
|
|
66
89
|
this.footer = {
|
|
67
90
|
type: 'context',
|
|
68
|
-
elements: [
|
|
69
|
-
type: footer ? 'plain_text' : 'mrkdwn',
|
|
70
|
-
text: footer || `Generated by <${this.gitHubServerUrl}/${repoName}|${repoName}>`
|
|
71
|
-
}],
|
|
91
|
+
elements: [element],
|
|
72
92
|
}
|
|
73
93
|
}
|
|
74
94
|
|
|
@@ -109,68 +129,65 @@ export class SlackMessageBuilder implements SlackMessage {
|
|
|
109
129
|
}
|
|
110
130
|
text.push(runText)
|
|
111
131
|
}
|
|
112
|
-
return text.join('\n')
|
|
132
|
+
return text.join('\n\n')
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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}`)
|
|
120
142
|
}
|
|
121
|
-
return
|
|
143
|
+
return resultProcessor(
|
|
144
|
+
stats.length == 0 ? 'No issues found' : stats.join(', ')
|
|
145
|
+
)
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
private composeSummary(): string {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
for (const [toolName, map] of data.entries()) {
|
|
140
|
-
summaries.push(this.composeRunSummary(toolName, map))
|
|
141
|
-
}
|
|
142
|
-
return summaries.join('\n')
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private tryGetLevel(run: Run, result: Result): string {
|
|
146
|
-
if (result.level) {
|
|
147
|
-
return result.level
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const ruleData: RuleData = {}
|
|
151
|
-
|
|
152
|
-
if (result.rule) {
|
|
153
|
-
if (result.rule?.index) {
|
|
154
|
-
ruleData.index = result.rule.index
|
|
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
|
|
155
163
|
}
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
158
177
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return rule.properties['problem.severity'] as string
|
|
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
|
|
171
189
|
}
|
|
172
190
|
}
|
|
173
|
-
|
|
174
|
-
return 'unknown'
|
|
191
|
+
return summaries.join('\n\n')
|
|
175
192
|
}
|
|
176
193
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```typescript
|
|
12
|
-
* import { SarifToSlackService } from 'sarif-to-slack';
|
|
12
|
+
* import { SarifToSlackService, FooterType } from '@fabasoad/sarif-to-slack';
|
|
13
13
|
*
|
|
14
|
-
* const service =
|
|
14
|
+
* const service = await SarifToSlackService.create({
|
|
15
15
|
* webhookUrl: 'https://hooks.slack.com/services/your/webhook/url',
|
|
16
16
|
* sarifPath: 'path/to/your/sarif/file.sarif',
|
|
17
|
-
*
|
|
17
|
+
* log: {
|
|
18
|
+
* level: 'info',
|
|
19
|
+
* template: '[{{logLevelName}}] [{{name}}] {{dateIsoStr}} ',
|
|
20
|
+
* colored: false,
|
|
21
|
+
* },
|
|
18
22
|
* username: 'SARIF Bot',
|
|
19
23
|
* iconUrl: 'https://example.com/icon.png',
|
|
20
24
|
* color: '#36a64f',
|
|
@@ -24,6 +28,7 @@
|
|
|
24
28
|
* },
|
|
25
29
|
* footer: {
|
|
26
30
|
* include: true,
|
|
31
|
+
* type: FooterType.PLAIN_TEXT,
|
|
27
32
|
* value: 'Generated by @fabasoad/sarif-to-slack'
|
|
28
33
|
* },
|
|
29
34
|
* actor: {
|
|
@@ -43,10 +48,16 @@
|
|
|
43
48
|
*/
|
|
44
49
|
export { SarifToSlackService } from './SarifToSlackService'
|
|
45
50
|
export {
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
CalculateResultsBy,
|
|
52
|
+
FooterOptions,
|
|
53
|
+
FooterType,
|
|
54
|
+
GroupResultsBy,
|
|
55
|
+
IncludeAwareOptions,
|
|
56
|
+
IncludeAwareWithValueOptions,
|
|
48
57
|
LogLevel,
|
|
49
|
-
|
|
58
|
+
LogOptions,
|
|
59
|
+
SarifLog,
|
|
60
|
+
SarifToSlackOutput,
|
|
50
61
|
SarifToSlackServiceOptions,
|
|
51
62
|
SlackMessage,
|
|
52
63
|
} from './types'
|