github-issue-tower-defence-management 1.148.4 → 1.148.5
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 +7 -0
- package/bin/domain/usecases/DailySecurityScanUseCase.js +40 -5
- package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/usecases/DailySecurityScanUseCase.test.ts +226 -5
- package/src/domain/usecases/DailySecurityScanUseCase.ts +65 -8
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts +1 -0
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.148.5](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.148.4...v1.148.5) (2026-08-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **security-scan:** report KEV additions only for products present in the workspace ([#1475](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1475)) ([70b9442](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/70b9442f21c46948319a32f8f296b1e290f450ec))
|
|
7
|
+
|
|
1
8
|
## [1.148.4](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.148.3...v1.148.4) (2026-08-09)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -7,6 +7,8 @@ const isKevVulnerability = (value) => {
|
|
|
7
7
|
}
|
|
8
8
|
const record = { ...value };
|
|
9
9
|
return (typeof record.cveID === 'string' &&
|
|
10
|
+
typeof record.vendorProject === 'string' &&
|
|
11
|
+
typeof record.product === 'string' &&
|
|
10
12
|
typeof record.vulnerabilityName === 'string' &&
|
|
11
13
|
typeof record.dateAdded === 'string');
|
|
12
14
|
};
|
|
@@ -94,13 +96,46 @@ class DailySecurityScanUseCase {
|
|
|
94
96
|
if (!isKevCatalog(parsedKev)) {
|
|
95
97
|
throw new Error(`Unexpected CISA KEV catalog format from ${KEV_CATALOG_URL}`);
|
|
96
98
|
}
|
|
97
|
-
const newKevEntries = parsedKev.vulnerabilities
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const newKevEntries = parsedKev.vulnerabilities.filter((vulnerability) => vulnerability.dateAdded >= yesterdayYmd);
|
|
100
|
+
const usedKevEntries = [];
|
|
101
|
+
for (const vulnerability of newKevEntries) {
|
|
102
|
+
if (await this.isProductPresentInScannedWorkspace(config.scanBaseDirectory, vulnerability.product)) {
|
|
103
|
+
usedKevEntries.push(vulnerability);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (usedKevEntries.length === 0) {
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
103
|
-
await this.issueRepository.createNewIssue(org, config.kevReportRepo, `CISA KEV new additions since ${yesterdayYmd}`,
|
|
109
|
+
await this.issueRepository.createNewIssue(org, config.kevReportRepo, `CISA KEV new additions since ${yesterdayYmd}`, usedKevEntries
|
|
110
|
+
.map((vulnerability) => `- ${vulnerability.dateAdded} ${vulnerability.cveID} ${vulnerability.vulnerabilityName}`)
|
|
111
|
+
.join('\n'), [manager], []);
|
|
112
|
+
};
|
|
113
|
+
this.isProductPresentInScannedWorkspace = async (scanBaseDirectory, product) => {
|
|
114
|
+
const { stdout: findOutput } = await this.localCommandRunner.runCommand('find', [scanBaseDirectory, '-maxdepth', '3', '-name', '.git', '-type', 'd']);
|
|
115
|
+
const repositoryDirectories = findOutput
|
|
116
|
+
.split('\n')
|
|
117
|
+
.filter((line) => line.length > 0)
|
|
118
|
+
.map((gitDirectory) => gitDirectory.replace(/\/\.git$/, ''));
|
|
119
|
+
for (const repositoryDirectory of repositoryDirectories) {
|
|
120
|
+
const { stderr, exitCode } = await this.localCommandRunner.runCommand('git', [
|
|
121
|
+
'-C',
|
|
122
|
+
repositoryDirectory,
|
|
123
|
+
'grep',
|
|
124
|
+
'-I',
|
|
125
|
+
'-i',
|
|
126
|
+
'-q',
|
|
127
|
+
'-F',
|
|
128
|
+
'-e',
|
|
129
|
+
product,
|
|
130
|
+
]);
|
|
131
|
+
if (exitCode === 0) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (exitCode !== 1) {
|
|
135
|
+
console.error(`Failed to search ${repositoryDirectory} for ${product}: ${stderr}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
104
139
|
};
|
|
105
140
|
}
|
|
106
141
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DailySecurityScanUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"DailySecurityScanUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -285,21 +285,28 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
285
285
|
mockHttpRepository,
|
|
286
286
|
} = buildUseCase();
|
|
287
287
|
|
|
288
|
-
mockLocalCommandRunner.runCommand.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
288
|
+
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
289
|
+
async (program, args) => {
|
|
290
|
+
if (program === 'find' && args.includes('-maxdepth')) {
|
|
291
|
+
return { stdout: '/repos/app/.git\n', stderr: '', exitCode: 0 };
|
|
292
|
+
}
|
|
293
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
294
|
+
},
|
|
295
|
+
);
|
|
293
296
|
mockHttpRepository.get.mockResolvedValue(
|
|
294
297
|
JSON.stringify({
|
|
295
298
|
vulnerabilities: [
|
|
296
299
|
{
|
|
297
300
|
cveID: 'CVE-2024-0001',
|
|
301
|
+
vendorProject: 'Example',
|
|
302
|
+
product: 'NewProduct',
|
|
298
303
|
vulnerabilityName: 'New Vulnerability',
|
|
299
304
|
dateAdded: '2024-01-02',
|
|
300
305
|
},
|
|
301
306
|
{
|
|
302
307
|
cveID: 'CVE-2023-9999',
|
|
308
|
+
vendorProject: 'Example',
|
|
309
|
+
product: 'OldProduct',
|
|
303
310
|
vulnerabilityName: 'Old Vulnerability',
|
|
304
311
|
dateAdded: '2023-12-31',
|
|
305
312
|
},
|
|
@@ -357,6 +364,8 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
357
364
|
vulnerabilities: [
|
|
358
365
|
{
|
|
359
366
|
cveID: 'CVE-2023-9999',
|
|
367
|
+
vendorProject: 'Example',
|
|
368
|
+
product: 'OldProduct',
|
|
360
369
|
vulnerabilityName: 'Old Vulnerability',
|
|
361
370
|
dateAdded: '2023-12-31',
|
|
362
371
|
},
|
|
@@ -381,6 +390,218 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
381
390
|
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(0);
|
|
382
391
|
});
|
|
383
392
|
|
|
393
|
+
it('does not create a KEV report issue when the product is absent from the scanned workspace', async () => {
|
|
394
|
+
const {
|
|
395
|
+
useCase,
|
|
396
|
+
mockLocalCommandRunner,
|
|
397
|
+
mockIssueRepository,
|
|
398
|
+
mockHttpRepository,
|
|
399
|
+
} = buildUseCase();
|
|
400
|
+
|
|
401
|
+
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
402
|
+
async (program, args) => {
|
|
403
|
+
if (program === 'find' && args.includes('-maxdepth')) {
|
|
404
|
+
return {
|
|
405
|
+
stdout: '/repos/app/.git\n/repos/site/.git\n',
|
|
406
|
+
stderr: '',
|
|
407
|
+
exitCode: 0,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (program === 'git' && args.includes('grep')) {
|
|
411
|
+
return { stdout: '', stderr: '', exitCode: 1 };
|
|
412
|
+
}
|
|
413
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
414
|
+
},
|
|
415
|
+
);
|
|
416
|
+
mockHttpRepository.get.mockResolvedValue(
|
|
417
|
+
JSON.stringify({
|
|
418
|
+
vulnerabilities: [
|
|
419
|
+
{
|
|
420
|
+
cveID: 'CVE-2024-0001',
|
|
421
|
+
vendorProject: 'Progress',
|
|
422
|
+
product: 'LoadMaster',
|
|
423
|
+
vulnerabilityName: 'Progress LoadMaster Command Injection',
|
|
424
|
+
dateAdded: '2024-01-02',
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
await useCase.run({
|
|
431
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
432
|
+
org: 'example-org',
|
|
433
|
+
manager: 'manager-name',
|
|
434
|
+
dailySecurityScan: {
|
|
435
|
+
scanBaseDirectory: '/repos',
|
|
436
|
+
targetHourUtc: 5,
|
|
437
|
+
enableKevNvdReport: true,
|
|
438
|
+
kevReportRepo: 'security-reports',
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const gitGrepCalls = mockLocalCommandRunner.runCommand.mock.calls.filter(
|
|
443
|
+
(call) => call[0] === 'git' && call[1].includes('grep'),
|
|
444
|
+
);
|
|
445
|
+
expect(gitGrepCalls.map((call) => call[1])).toEqual([
|
|
446
|
+
[
|
|
447
|
+
'-C',
|
|
448
|
+
'/repos/app',
|
|
449
|
+
'grep',
|
|
450
|
+
'-I',
|
|
451
|
+
'-i',
|
|
452
|
+
'-q',
|
|
453
|
+
'-F',
|
|
454
|
+
'-e',
|
|
455
|
+
'LoadMaster',
|
|
456
|
+
],
|
|
457
|
+
[
|
|
458
|
+
'-C',
|
|
459
|
+
'/repos/site',
|
|
460
|
+
'grep',
|
|
461
|
+
'-I',
|
|
462
|
+
'-i',
|
|
463
|
+
'-q',
|
|
464
|
+
'-F',
|
|
465
|
+
'-e',
|
|
466
|
+
'LoadMaster',
|
|
467
|
+
],
|
|
468
|
+
]);
|
|
469
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(0);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('reports only the new KEV entries whose product is present in the scanned workspace', async () => {
|
|
473
|
+
const {
|
|
474
|
+
useCase,
|
|
475
|
+
mockLocalCommandRunner,
|
|
476
|
+
mockIssueRepository,
|
|
477
|
+
mockHttpRepository,
|
|
478
|
+
} = buildUseCase();
|
|
479
|
+
|
|
480
|
+
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
481
|
+
async (program, args) => {
|
|
482
|
+
if (program === 'find' && args.includes('-maxdepth')) {
|
|
483
|
+
return { stdout: '/repos/app/.git\n', stderr: '', exitCode: 0 };
|
|
484
|
+
}
|
|
485
|
+
if (program === 'git' && args.includes('grep')) {
|
|
486
|
+
return {
|
|
487
|
+
stdout: '',
|
|
488
|
+
stderr: '',
|
|
489
|
+
exitCode: args.includes('LoadMaster') ? 1 : 0,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
493
|
+
},
|
|
494
|
+
);
|
|
495
|
+
mockHttpRepository.get.mockResolvedValue(
|
|
496
|
+
JSON.stringify({
|
|
497
|
+
vulnerabilities: [
|
|
498
|
+
{
|
|
499
|
+
cveID: 'CVE-2024-0001',
|
|
500
|
+
vendorProject: 'Progress',
|
|
501
|
+
product: 'LoadMaster',
|
|
502
|
+
vulnerabilityName: 'Progress LoadMaster Command Injection',
|
|
503
|
+
dateAdded: '2024-01-02',
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
cveID: 'CVE-2024-0002',
|
|
507
|
+
vendorProject: 'Example',
|
|
508
|
+
product: 'ExampleLibrary',
|
|
509
|
+
vulnerabilityName: 'Example Library Deserialization',
|
|
510
|
+
dateAdded: '2024-01-02',
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
await useCase.run({
|
|
517
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
518
|
+
org: 'example-org',
|
|
519
|
+
manager: 'manager-name',
|
|
520
|
+
dailySecurityScan: {
|
|
521
|
+
scanBaseDirectory: '/repos',
|
|
522
|
+
targetHourUtc: 5,
|
|
523
|
+
enableKevNvdReport: true,
|
|
524
|
+
kevReportRepo: 'security-reports',
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
|
|
529
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).toContain(
|
|
530
|
+
'CVE-2024-0002',
|
|
531
|
+
);
|
|
532
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).not.toContain(
|
|
533
|
+
'CVE-2024-0001',
|
|
534
|
+
);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('logs a failed repository search and keeps searching the remaining repositories', async () => {
|
|
538
|
+
const {
|
|
539
|
+
useCase,
|
|
540
|
+
mockLocalCommandRunner,
|
|
541
|
+
mockIssueRepository,
|
|
542
|
+
mockHttpRepository,
|
|
543
|
+
} = buildUseCase();
|
|
544
|
+
const errorSpy = jest
|
|
545
|
+
.spyOn(console, 'error')
|
|
546
|
+
.mockImplementation(() => undefined);
|
|
547
|
+
|
|
548
|
+
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
549
|
+
async (program, args) => {
|
|
550
|
+
if (program === 'find' && args.includes('-maxdepth')) {
|
|
551
|
+
return {
|
|
552
|
+
stdout: '/repos/broken/.git\n/repos/app/.git\n',
|
|
553
|
+
stderr: '',
|
|
554
|
+
exitCode: 0,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (program === 'git' && args.includes('grep')) {
|
|
558
|
+
return args.includes('/repos/broken')
|
|
559
|
+
? {
|
|
560
|
+
stdout: '',
|
|
561
|
+
stderr: 'fatal: not a git repository',
|
|
562
|
+
exitCode: 128,
|
|
563
|
+
}
|
|
564
|
+
: { stdout: '', stderr: '', exitCode: 0 };
|
|
565
|
+
}
|
|
566
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
mockHttpRepository.get.mockResolvedValue(
|
|
570
|
+
JSON.stringify({
|
|
571
|
+
vulnerabilities: [
|
|
572
|
+
{
|
|
573
|
+
cveID: 'CVE-2024-0001',
|
|
574
|
+
vendorProject: 'Progress',
|
|
575
|
+
product: 'LoadMaster',
|
|
576
|
+
vulnerabilityName: 'Progress LoadMaster Command Injection',
|
|
577
|
+
dateAdded: '2024-01-02',
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
await useCase.run({
|
|
584
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
585
|
+
org: 'example-org',
|
|
586
|
+
manager: 'manager-name',
|
|
587
|
+
dailySecurityScan: {
|
|
588
|
+
scanBaseDirectory: '/repos',
|
|
589
|
+
targetHourUtc: 5,
|
|
590
|
+
enableKevNvdReport: true,
|
|
591
|
+
kevReportRepo: 'security-reports',
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
596
|
+
'Failed to search /repos/broken for LoadMaster: fatal: not a git repository',
|
|
597
|
+
);
|
|
598
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
|
|
599
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).toContain(
|
|
600
|
+
'CVE-2024-0001',
|
|
601
|
+
);
|
|
602
|
+
errorSpy.mockRestore();
|
|
603
|
+
});
|
|
604
|
+
|
|
384
605
|
it('throws when the KEV catalog format is unexpected', async () => {
|
|
385
606
|
const { useCase, mockLocalCommandRunner, mockHttpRepository } =
|
|
386
607
|
buildUseCase();
|
|
@@ -12,6 +12,8 @@ export type DailySecurityScanConfig = {
|
|
|
12
12
|
|
|
13
13
|
type KevVulnerability = {
|
|
14
14
|
cveID: string;
|
|
15
|
+
vendorProject: string;
|
|
16
|
+
product: string;
|
|
15
17
|
vulnerabilityName: string;
|
|
16
18
|
dateAdded: string;
|
|
17
19
|
};
|
|
@@ -27,6 +29,8 @@ const isKevVulnerability = (value: unknown): value is KevVulnerability => {
|
|
|
27
29
|
const record: Record<string, unknown> = { ...value };
|
|
28
30
|
return (
|
|
29
31
|
typeof record.cveID === 'string' &&
|
|
32
|
+
typeof record.vendorProject === 'string' &&
|
|
33
|
+
typeof record.product === 'string' &&
|
|
30
34
|
typeof record.vulnerabilityName === 'string' &&
|
|
31
35
|
typeof record.dateAdded === 'string'
|
|
32
36
|
);
|
|
@@ -178,13 +182,21 @@ export class DailySecurityScanUseCase {
|
|
|
178
182
|
);
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
const newKevEntries = parsedKev.vulnerabilities
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
const newKevEntries = parsedKev.vulnerabilities.filter(
|
|
186
|
+
(vulnerability) => vulnerability.dateAdded >= yesterdayYmd,
|
|
187
|
+
);
|
|
188
|
+
const usedKevEntries: KevVulnerability[] = [];
|
|
189
|
+
for (const vulnerability of newKevEntries) {
|
|
190
|
+
if (
|
|
191
|
+
await this.isProductPresentInScannedWorkspace(
|
|
192
|
+
config.scanBaseDirectory,
|
|
193
|
+
vulnerability.product,
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
usedKevEntries.push(vulnerability);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (usedKevEntries.length === 0) {
|
|
188
200
|
return;
|
|
189
201
|
}
|
|
190
202
|
|
|
@@ -192,9 +204,54 @@ export class DailySecurityScanUseCase {
|
|
|
192
204
|
org,
|
|
193
205
|
config.kevReportRepo,
|
|
194
206
|
`CISA KEV new additions since ${yesterdayYmd}`,
|
|
195
|
-
|
|
207
|
+
usedKevEntries
|
|
208
|
+
.map(
|
|
209
|
+
(vulnerability) =>
|
|
210
|
+
`- ${vulnerability.dateAdded} ${vulnerability.cveID} ${vulnerability.vulnerabilityName}`,
|
|
211
|
+
)
|
|
212
|
+
.join('\n'),
|
|
196
213
|
[manager],
|
|
197
214
|
[],
|
|
198
215
|
);
|
|
199
216
|
};
|
|
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
|
+
};
|
|
200
257
|
}
|
|
@@ -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;AA0CF,qBAAa,wBAAwB;IAEjC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB;IAC/C,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC;IACjE,QAAQ,CAAC,cAAc,EAAE,cAAc;gBAF9B,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,EACxD,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,CA0Bf;IAEF,OAAO,CAAC,gBAAgB,CAoEtB;IAEF,OAAO,CAAC,kBAAkB,CAqDxB;IAEF,OAAO,CAAC,kCAAkC,CAsCxC;CACH"}
|