github-issue-tower-defence-management 1.148.5 → 1.148.7
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/CHANGELOG.md +14 -0
- package/bin/domain/usecases/DailySecurityScanUseCase.js +88 -44
- package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/usecases/DailySecurityScanUseCase.test.ts +499 -197
- package/src/domain/usecases/DailySecurityScanUseCase.ts +156 -75
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts +2 -3
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
|
@@ -47,13 +47,88 @@ const isKevCatalog = (value: unknown): value is KevCatalog => {
|
|
|
47
47
|
);
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
type ScannedVulnerablePackage = {
|
|
51
|
+
repositoryName: string;
|
|
52
|
+
ecosystem: string;
|
|
53
|
+
packageName: string;
|
|
54
|
+
packageVersion: string;
|
|
55
|
+
vulnerabilityId: string;
|
|
56
|
+
vulnerabilityIdentifiers: string[];
|
|
57
|
+
summary: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const parseScannerVulnerabilities = (
|
|
61
|
+
repositoryName: string,
|
|
62
|
+
scannerOutput: string,
|
|
63
|
+
): ScannedVulnerablePackage[] => {
|
|
64
|
+
let parsed: unknown;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(scannerOutput);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(
|
|
69
|
+
`Unparsable osv-scanner output for ${repositoryName}: ${String(error)}`,
|
|
70
|
+
);
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const toRecord = (value: unknown): Record<string, unknown> =>
|
|
74
|
+
typeof value === 'object' && value !== null ? { ...value } : {};
|
|
75
|
+
const readArray = (value: unknown, key: string): unknown[] => {
|
|
76
|
+
const entry = toRecord(value)[key];
|
|
77
|
+
return Array.isArray(entry) ? entry : [];
|
|
78
|
+
};
|
|
79
|
+
const readString = (value: unknown, key: string): string => {
|
|
80
|
+
const entry = toRecord(value)[key];
|
|
81
|
+
return typeof entry === 'string' ? entry : '';
|
|
82
|
+
};
|
|
83
|
+
return readArray(parsed, 'results').flatMap((result) =>
|
|
84
|
+
readArray(result, 'packages').flatMap((scannedPackage) => {
|
|
85
|
+
const packageDetail = toRecord(scannedPackage).package;
|
|
86
|
+
return readArray(scannedPackage, 'vulnerabilities').map(
|
|
87
|
+
(vulnerability) => {
|
|
88
|
+
const vulnerabilityId = readString(vulnerability, 'id');
|
|
89
|
+
const aliases = readArray(vulnerability, 'aliases').filter(
|
|
90
|
+
(alias): alias is string => typeof alias === 'string',
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
repositoryName,
|
|
94
|
+
ecosystem: readString(packageDetail, 'ecosystem'),
|
|
95
|
+
packageName: readString(packageDetail, 'name'),
|
|
96
|
+
packageVersion: readString(packageDetail, 'version'),
|
|
97
|
+
vulnerabilityId,
|
|
98
|
+
vulnerabilityIdentifiers: [vulnerabilityId, ...aliases],
|
|
99
|
+
summary: readString(vulnerability, 'summary'),
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const renderScannerFindings = (
|
|
108
|
+
today: string,
|
|
109
|
+
vulnerablePackages: ScannedVulnerablePackage[],
|
|
110
|
+
): string =>
|
|
111
|
+
[
|
|
112
|
+
'## OSV-Scanner findings',
|
|
113
|
+
'',
|
|
114
|
+
`### ${today}`,
|
|
115
|
+
'',
|
|
116
|
+
...vulnerablePackages.map(
|
|
117
|
+
(vulnerablePackage) =>
|
|
118
|
+
`- ${vulnerablePackage.ecosystem} ${vulnerablePackage.packageName} ${vulnerablePackage.packageVersion} ${vulnerablePackage.vulnerabilityIdentifiers.join(' ')} ${vulnerablePackage.summary}`,
|
|
119
|
+
),
|
|
120
|
+
].join('\n');
|
|
121
|
+
|
|
50
122
|
const KEV_CATALOG_URL =
|
|
51
123
|
'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
|
|
52
124
|
|
|
53
125
|
export class DailySecurityScanUseCase {
|
|
54
126
|
constructor(
|
|
55
127
|
readonly localCommandRunner: LocalCommandRunner,
|
|
56
|
-
readonly issueRepository: Pick<
|
|
128
|
+
readonly issueRepository: Pick<
|
|
129
|
+
IssueRepository,
|
|
130
|
+
'createNewIssue' | 'searchIssue' | 'createCommentByUrl'
|
|
131
|
+
>,
|
|
57
132
|
readonly httpRepository: HttpRepository,
|
|
58
133
|
) {}
|
|
59
134
|
|
|
@@ -75,7 +150,7 @@ export class DailySecurityScanUseCase {
|
|
|
75
150
|
const lastTargetDate = input.targetDates[input.targetDates.length - 1];
|
|
76
151
|
const today = lastTargetDate.toISOString().slice(0, 10);
|
|
77
152
|
|
|
78
|
-
await this.scanRepositories(
|
|
153
|
+
const scannedVulnerablePackages = await this.scanRepositories(
|
|
79
154
|
input.org,
|
|
80
155
|
input.manager,
|
|
81
156
|
today,
|
|
@@ -87,6 +162,7 @@ export class DailySecurityScanUseCase {
|
|
|
87
162
|
input.manager,
|
|
88
163
|
lastTargetDate,
|
|
89
164
|
input.dailySecurityScan,
|
|
165
|
+
scannedVulnerablePackages,
|
|
90
166
|
);
|
|
91
167
|
};
|
|
92
168
|
|
|
@@ -95,15 +171,13 @@ export class DailySecurityScanUseCase {
|
|
|
95
171
|
manager: Member['name'],
|
|
96
172
|
today: string,
|
|
97
173
|
config: DailySecurityScanConfig,
|
|
98
|
-
): Promise<
|
|
174
|
+
): Promise<ScannedVulnerablePackage[]> => {
|
|
99
175
|
const { stdout: findOutput } = await this.localCommandRunner.runCommand(
|
|
100
176
|
'find',
|
|
101
177
|
[
|
|
102
178
|
config.scanBaseDirectory,
|
|
103
|
-
'-mindepth',
|
|
104
|
-
'4',
|
|
105
179
|
'-maxdepth',
|
|
106
|
-
'
|
|
180
|
+
'5',
|
|
107
181
|
'-name',
|
|
108
182
|
'.git',
|
|
109
183
|
'-type',
|
|
@@ -116,6 +190,14 @@ export class DailySecurityScanUseCase {
|
|
|
116
190
|
.filter((line) => line.length > 0)
|
|
117
191
|
.map((gitDirectory) => gitDirectory.replace(/\/\.git$/, ''));
|
|
118
192
|
|
|
193
|
+
if (repositoryDirectories.length === 0) {
|
|
194
|
+
console.error(
|
|
195
|
+
`No repositories found in scan base directory: ${config.scanBaseDirectory}`,
|
|
196
|
+
);
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const scannedVulnerablePackages: ScannedVulnerablePackage[] = [];
|
|
119
201
|
for (const repositoryDirectory of repositoryDirectories) {
|
|
120
202
|
const { stdout: remoteUrl, exitCode: remoteExitCode } =
|
|
121
203
|
await this.localCommandRunner.runCommand('git', [
|
|
@@ -138,26 +220,62 @@ export class DailySecurityScanUseCase {
|
|
|
138
220
|
const repositoryOrg = remoteMatch[1];
|
|
139
221
|
const repositoryName = remoteMatch[2];
|
|
140
222
|
|
|
141
|
-
const {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
223
|
+
const {
|
|
224
|
+
stdout: scanOutput,
|
|
225
|
+
stderr: scanStderr,
|
|
226
|
+
exitCode: scanExitCode,
|
|
227
|
+
} = await this.localCommandRunner.runCommand('osv-scanner', [
|
|
228
|
+
'scan',
|
|
229
|
+
'source',
|
|
230
|
+
'-r',
|
|
231
|
+
repositoryDirectory,
|
|
232
|
+
'--format',
|
|
233
|
+
'json',
|
|
234
|
+
]);
|
|
235
|
+
if (scanExitCode === 0) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
148
238
|
if (scanExitCode !== 1) {
|
|
239
|
+
console.error(
|
|
240
|
+
`osv-scanner failed with exit code ${scanExitCode} for ${repositoryDirectory}: ${scanStderr}`,
|
|
241
|
+
);
|
|
149
242
|
continue;
|
|
150
243
|
}
|
|
151
244
|
|
|
152
|
-
|
|
153
|
-
|
|
245
|
+
const vulnerablePackages = parseScannerVulnerabilities(
|
|
246
|
+
repositoryName,
|
|
247
|
+
scanOutput,
|
|
248
|
+
);
|
|
249
|
+
scannedVulnerablePackages.push(...vulnerablePackages);
|
|
250
|
+
|
|
251
|
+
const findingsBody = renderScannerFindings(today, vulnerablePackages);
|
|
252
|
+
const existingIssues = await this.issueRepository.searchIssue({
|
|
253
|
+
owner: repositoryOrg,
|
|
154
254
|
repositoryName,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
255
|
+
type: 'issue',
|
|
256
|
+
state: 'open',
|
|
257
|
+
title: 'Daily security scan findings',
|
|
258
|
+
});
|
|
259
|
+
const existingIssue = existingIssues.find(
|
|
260
|
+
(issue) => issue.title === 'Daily security scan findings',
|
|
159
261
|
);
|
|
262
|
+
if (existingIssue) {
|
|
263
|
+
await this.issueRepository.createCommentByUrl(
|
|
264
|
+
existingIssue.url,
|
|
265
|
+
findingsBody,
|
|
266
|
+
);
|
|
267
|
+
} else {
|
|
268
|
+
await this.issueRepository.createNewIssue(
|
|
269
|
+
repositoryOrg,
|
|
270
|
+
repositoryName,
|
|
271
|
+
'Daily security scan findings',
|
|
272
|
+
findingsBody,
|
|
273
|
+
[manager],
|
|
274
|
+
[],
|
|
275
|
+
);
|
|
276
|
+
}
|
|
160
277
|
}
|
|
278
|
+
return scannedVulnerablePackages;
|
|
161
279
|
};
|
|
162
280
|
|
|
163
281
|
private reportKevAdditions = async (
|
|
@@ -165,6 +283,7 @@ export class DailySecurityScanUseCase {
|
|
|
165
283
|
manager: Member['name'],
|
|
166
284
|
lastTargetDate: Date,
|
|
167
285
|
config: DailySecurityScanConfig,
|
|
286
|
+
scannedVulnerablePackages: ScannedVulnerablePackage[],
|
|
168
287
|
): Promise<void> => {
|
|
169
288
|
if (!config.enableKevNvdReport || !config.kevReportRepo) {
|
|
170
289
|
return;
|
|
@@ -185,18 +304,15 @@ export class DailySecurityScanUseCase {
|
|
|
185
304
|
const newKevEntries = parsedKev.vulnerabilities.filter(
|
|
186
305
|
(vulnerability) => vulnerability.dateAdded >= yesterdayYmd,
|
|
187
306
|
);
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (usedKevEntries.length === 0) {
|
|
307
|
+
const affectingKevEntries = newKevEntries
|
|
308
|
+
.map((vulnerability) => ({
|
|
309
|
+
vulnerability,
|
|
310
|
+
affectedPackages: scannedVulnerablePackages.filter((scannedPackage) =>
|
|
311
|
+
scannedPackage.vulnerabilityIdentifiers.includes(vulnerability.cveID),
|
|
312
|
+
),
|
|
313
|
+
}))
|
|
314
|
+
.filter((entry) => entry.affectedPackages.length > 0);
|
|
315
|
+
if (affectingKevEntries.length === 0) {
|
|
200
316
|
return;
|
|
201
317
|
}
|
|
202
318
|
|
|
@@ -204,54 +320,19 @@ export class DailySecurityScanUseCase {
|
|
|
204
320
|
org,
|
|
205
321
|
config.kevReportRepo,
|
|
206
322
|
`CISA KEV new additions since ${yesterdayYmd}`,
|
|
207
|
-
|
|
208
|
-
.map(
|
|
209
|
-
|
|
210
|
-
`- ${vulnerability.dateAdded} ${vulnerability.cveID} ${vulnerability.vulnerabilityName}`,
|
|
323
|
+
affectingKevEntries
|
|
324
|
+
.map((entry) =>
|
|
325
|
+
[
|
|
326
|
+
`- ${entry.vulnerability.dateAdded} ${entry.vulnerability.cveID} ${entry.vulnerability.vulnerabilityName}`,
|
|
327
|
+
...entry.affectedPackages.map(
|
|
328
|
+
(affectedPackage) =>
|
|
329
|
+
` - ${affectedPackage.repositoryName} ${affectedPackage.ecosystem} ${affectedPackage.packageName} ${affectedPackage.packageVersion}`,
|
|
330
|
+
),
|
|
331
|
+
].join('\n'),
|
|
211
332
|
)
|
|
212
333
|
.join('\n'),
|
|
213
334
|
[manager],
|
|
214
335
|
[],
|
|
215
336
|
);
|
|
216
337
|
};
|
|
217
|
-
|
|
218
|
-
private isProductPresentInScannedWorkspace = async (
|
|
219
|
-
scanBaseDirectory: string,
|
|
220
|
-
product: string,
|
|
221
|
-
): Promise<boolean> => {
|
|
222
|
-
const { stdout: findOutput } = await this.localCommandRunner.runCommand(
|
|
223
|
-
'find',
|
|
224
|
-
[scanBaseDirectory, '-maxdepth', '3', '-name', '.git', '-type', 'd'],
|
|
225
|
-
);
|
|
226
|
-
const repositoryDirectories = findOutput
|
|
227
|
-
.split('\n')
|
|
228
|
-
.filter((line) => line.length > 0)
|
|
229
|
-
.map((gitDirectory) => gitDirectory.replace(/\/\.git$/, ''));
|
|
230
|
-
|
|
231
|
-
for (const repositoryDirectory of repositoryDirectories) {
|
|
232
|
-
const { stderr, exitCode } = await this.localCommandRunner.runCommand(
|
|
233
|
-
'git',
|
|
234
|
-
[
|
|
235
|
-
'-C',
|
|
236
|
-
repositoryDirectory,
|
|
237
|
-
'grep',
|
|
238
|
-
'-I',
|
|
239
|
-
'-i',
|
|
240
|
-
'-q',
|
|
241
|
-
'-F',
|
|
242
|
-
'-e',
|
|
243
|
-
product,
|
|
244
|
-
],
|
|
245
|
-
);
|
|
246
|
-
if (exitCode === 0) {
|
|
247
|
-
return true;
|
|
248
|
-
}
|
|
249
|
-
if (exitCode !== 1) {
|
|
250
|
-
console.error(
|
|
251
|
-
`Failed to search ${repositoryDirectory} for ${product}: ${stderr}`,
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return false;
|
|
256
|
-
};
|
|
257
338
|
}
|
|
@@ -10,9 +10,9 @@ export type DailySecurityScanConfig = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare class DailySecurityScanUseCase {
|
|
12
12
|
readonly localCommandRunner: LocalCommandRunner;
|
|
13
|
-
readonly issueRepository: Pick<IssueRepository, 'createNewIssue'>;
|
|
13
|
+
readonly issueRepository: Pick<IssueRepository, 'createNewIssue' | 'searchIssue' | 'createCommentByUrl'>;
|
|
14
14
|
readonly httpRepository: HttpRepository;
|
|
15
|
-
constructor(localCommandRunner: LocalCommandRunner, issueRepository: Pick<IssueRepository, 'createNewIssue'>, httpRepository: HttpRepository);
|
|
15
|
+
constructor(localCommandRunner: LocalCommandRunner, issueRepository: Pick<IssueRepository, 'createNewIssue' | 'searchIssue' | 'createCommentByUrl'>, httpRepository: HttpRepository);
|
|
16
16
|
run: (input: {
|
|
17
17
|
targetDates: Date[];
|
|
18
18
|
org: string;
|
|
@@ -21,6 +21,5 @@ export declare class DailySecurityScanUseCase {
|
|
|
21
21
|
}) => Promise<void>;
|
|
22
22
|
private scanRepositories;
|
|
23
23
|
private reportKevAdditions;
|
|
24
|
-
private isProductPresentInScannedWorkspace;
|
|
25
24
|
}
|
|
26
25
|
//# sourceMappingURL=DailySecurityScanUseCase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DailySecurityScanUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,MAAM,uBAAuB,GAAG;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;
|
|
1
|
+
{"version":3,"file":"DailySecurityScanUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,MAAM,uBAAuB,GAAG;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAkHF,qBAAa,wBAAwB;IAEjC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB;IAC/C,QAAQ,CAAC,eAAe,EAAE,IAAI,CAC5B,eAAe,EACf,gBAAgB,GAAG,aAAa,GAAG,oBAAoB,CACxD;IACD,QAAQ,CAAC,cAAc,EAAE,cAAc;gBAL9B,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,IAAI,CAC5B,eAAe,EACf,gBAAgB,GAAG,aAAa,GAAG,oBAAoB,CACxD,EACQ,cAAc,EAAE,cAAc;IAGzC,GAAG,GAAU,OAAO;QAClB,WAAW,EAAE,IAAI,EAAE,CAAC;QACpB,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,iBAAiB,EAAE,uBAAuB,CAAC;KAC5C,KAAG,OAAO,CAAC,IAAI,CAAC,CA2Bf;IAEF,OAAO,CAAC,gBAAgB,CA8GtB;IAEF,OAAO,CAAC,kBAAkB,CAwDxB;CACH"}
|