github-issue-tower-defence-management 1.148.5 → 1.148.6
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 +25 -5
- package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/usecases/DailySecurityScanUseCase.test.ts +257 -17
- package/src/domain/usecases/DailySecurityScanUseCase.ts +52 -17
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts +2 -2
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.148.6](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.148.5...v1.148.6) (2026-08-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **security-scan:** fix silent scan failures and issue deduplication ([#1479](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1479)) ([7cdc714](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/7cdc7149fe1420bfb4581014220e8b70c6043b34))
|
|
7
|
+
|
|
1
8
|
## [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
9
|
|
|
3
10
|
|
|
@@ -40,10 +40,8 @@ class DailySecurityScanUseCase {
|
|
|
40
40
|
this.scanRepositories = async (org, manager, today, config) => {
|
|
41
41
|
const { stdout: findOutput } = await this.localCommandRunner.runCommand('find', [
|
|
42
42
|
config.scanBaseDirectory,
|
|
43
|
-
'-mindepth',
|
|
44
|
-
'4',
|
|
45
43
|
'-maxdepth',
|
|
46
|
-
'
|
|
44
|
+
'5',
|
|
47
45
|
'-name',
|
|
48
46
|
'.git',
|
|
49
47
|
'-type',
|
|
@@ -53,6 +51,10 @@ class DailySecurityScanUseCase {
|
|
|
53
51
|
.split('\n')
|
|
54
52
|
.filter((line) => line.length > 0)
|
|
55
53
|
.map((gitDirectory) => gitDirectory.replace(/\/\.git$/, ''));
|
|
54
|
+
if (repositoryDirectories.length === 0) {
|
|
55
|
+
console.error(`No repositories found in scan base directory: ${config.scanBaseDirectory}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
56
58
|
for (const repositoryDirectory of repositoryDirectories) {
|
|
57
59
|
const { stdout: remoteUrl, exitCode: remoteExitCode } = await this.localCommandRunner.runCommand('git', [
|
|
58
60
|
'-C',
|
|
@@ -72,16 +74,34 @@ class DailySecurityScanUseCase {
|
|
|
72
74
|
}
|
|
73
75
|
const repositoryOrg = remoteMatch[1];
|
|
74
76
|
const repositoryName = remoteMatch[2];
|
|
75
|
-
const { stdout: scanOutput, exitCode: scanExitCode } = await this.localCommandRunner.runCommand('osv-scanner', [
|
|
77
|
+
const { stdout: scanOutput, stderr: scanStderr, exitCode: scanExitCode, } = await this.localCommandRunner.runCommand('osv-scanner', [
|
|
76
78
|
'scan',
|
|
77
79
|
'source',
|
|
78
80
|
'-r',
|
|
79
81
|
repositoryDirectory,
|
|
80
82
|
]);
|
|
83
|
+
if (scanExitCode === 0) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
81
86
|
if (scanExitCode !== 1) {
|
|
87
|
+
console.error(`osv-scanner failed with exit code ${scanExitCode} for ${repositoryDirectory}: ${scanStderr}`);
|
|
82
88
|
continue;
|
|
83
89
|
}
|
|
84
|
-
|
|
90
|
+
const findingsBody = `## OSV-Scanner findings\n\n### ${today}\n\n\`\`\`\n${scanOutput}\n\`\`\``;
|
|
91
|
+
const existingIssues = await this.issueRepository.searchIssue({
|
|
92
|
+
owner: repositoryOrg,
|
|
93
|
+
repositoryName,
|
|
94
|
+
type: 'issue',
|
|
95
|
+
state: 'open',
|
|
96
|
+
title: 'Daily security scan findings',
|
|
97
|
+
});
|
|
98
|
+
const existingIssue = existingIssues.find((issue) => issue.title === 'Daily security scan findings');
|
|
99
|
+
if (existingIssue) {
|
|
100
|
+
await this.issueRepository.createCommentByUrl(existingIssue.url, findingsBody);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await this.issueRepository.createNewIssue(repositoryOrg, repositoryName, 'Daily security scan findings', findingsBody, [manager], []);
|
|
104
|
+
}
|
|
85
105
|
}
|
|
86
106
|
};
|
|
87
107
|
this.reportKevAdditions = async (org, manager, lastTargetDate, config) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DailySecurityScanUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":";;;AAwBA,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAA6B,EAAE;IACvE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,GAAG,KAAK,EAAE,CAAC;IACrD,OAAO,CACL,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;QAChC,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ;QACxC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAClC,OAAO,MAAM,CAAC,iBAAiB,KAAK,QAAQ;QAC5C,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CACrC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAc,EAAuB,EAAE;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,GAAG,KAAK,EAAE,CAAC;IACrD,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;QACrC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,kBAAkB,CAAC,CACjD,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,eAAe,GACnB,qFAAqF,CAAC;AAExF,MAAa,wBAAwB;IACnC,YACW,kBAAsC,EACtC,
|
|
1
|
+
{"version":3,"file":"DailySecurityScanUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/DailySecurityScanUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -64,6 +64,8 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
64
64
|
const { useCase, mockLocalCommandRunner, mockIssueRepository } =
|
|
65
65
|
buildUseCase();
|
|
66
66
|
|
|
67
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
68
|
+
|
|
67
69
|
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
68
70
|
async (program, args) => {
|
|
69
71
|
if (program === 'find') {
|
|
@@ -107,7 +109,7 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
107
109
|
);
|
|
108
110
|
expect(mockIssueRepository.createNewIssue.mock.calls[0][1]).toBe('app');
|
|
109
111
|
expect(mockIssueRepository.createNewIssue.mock.calls[0][2]).toBe(
|
|
110
|
-
'Daily security scan findings
|
|
112
|
+
'Daily security scan findings',
|
|
111
113
|
);
|
|
112
114
|
expect(mockIssueRepository.createNewIssue.mock.calls[0][4]).toEqual([
|
|
113
115
|
'manager-name',
|
|
@@ -233,10 +235,22 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
233
235
|
const { useCase, mockLocalCommandRunner, mockHttpRepository } =
|
|
234
236
|
buildUseCase();
|
|
235
237
|
|
|
236
|
-
mockLocalCommandRunner.runCommand.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
239
|
+
if (program === 'find') {
|
|
240
|
+
return {
|
|
241
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
242
|
+
stderr: '',
|
|
243
|
+
exitCode: 0,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (program === 'git') {
|
|
247
|
+
return {
|
|
248
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
249
|
+
stderr: '',
|
|
250
|
+
exitCode: 0,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
240
254
|
});
|
|
241
255
|
|
|
242
256
|
await useCase.run({
|
|
@@ -257,10 +271,22 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
257
271
|
const { useCase, mockLocalCommandRunner, mockHttpRepository } =
|
|
258
272
|
buildUseCase();
|
|
259
273
|
|
|
260
|
-
mockLocalCommandRunner.runCommand.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
274
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
275
|
+
if (program === 'find') {
|
|
276
|
+
return {
|
|
277
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
278
|
+
stderr: '',
|
|
279
|
+
exitCode: 0,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (program === 'git') {
|
|
283
|
+
return {
|
|
284
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
285
|
+
stderr: '',
|
|
286
|
+
exitCode: 0,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
264
290
|
});
|
|
265
291
|
|
|
266
292
|
await useCase.run({
|
|
@@ -354,10 +380,22 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
354
380
|
mockHttpRepository,
|
|
355
381
|
} = buildUseCase();
|
|
356
382
|
|
|
357
|
-
mockLocalCommandRunner.runCommand.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
383
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
384
|
+
if (program === 'find') {
|
|
385
|
+
return {
|
|
386
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
387
|
+
stderr: '',
|
|
388
|
+
exitCode: 0,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (program === 'git') {
|
|
392
|
+
return {
|
|
393
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
394
|
+
stderr: '',
|
|
395
|
+
exitCode: 0,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
361
399
|
});
|
|
362
400
|
mockHttpRepository.get.mockResolvedValue(
|
|
363
401
|
JSON.stringify({
|
|
@@ -606,10 +644,22 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
606
644
|
const { useCase, mockLocalCommandRunner, mockHttpRepository } =
|
|
607
645
|
buildUseCase();
|
|
608
646
|
|
|
609
|
-
mockLocalCommandRunner.runCommand.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
647
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
648
|
+
if (program === 'find') {
|
|
649
|
+
return {
|
|
650
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
651
|
+
stderr: '',
|
|
652
|
+
exitCode: 0,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (program === 'git') {
|
|
656
|
+
return {
|
|
657
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
658
|
+
stderr: '',
|
|
659
|
+
exitCode: 0,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
613
663
|
});
|
|
614
664
|
mockHttpRepository.get.mockResolvedValue(
|
|
615
665
|
JSON.stringify({ unexpected: 'structure' }),
|
|
@@ -629,5 +679,195 @@ describe('DailySecurityScanUseCase', () => {
|
|
|
629
679
|
}),
|
|
630
680
|
).rejects.toThrow('Unexpected CISA KEV catalog format');
|
|
631
681
|
});
|
|
682
|
+
|
|
683
|
+
it('logs an error and returns without throwing when no repositories are found in the scan base directory', async () => {
|
|
684
|
+
const { useCase, mockLocalCommandRunner } = buildUseCase();
|
|
685
|
+
const errorSpy = jest
|
|
686
|
+
.spyOn(console, 'error')
|
|
687
|
+
.mockImplementation(() => undefined);
|
|
688
|
+
|
|
689
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
690
|
+
if (program === 'find') {
|
|
691
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
692
|
+
}
|
|
693
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
await expect(
|
|
697
|
+
useCase.run({
|
|
698
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
699
|
+
org: 'example-org',
|
|
700
|
+
manager: 'manager-name',
|
|
701
|
+
dailySecurityScan: {
|
|
702
|
+
scanBaseDirectory: '/repos',
|
|
703
|
+
targetHourUtc: 5,
|
|
704
|
+
},
|
|
705
|
+
}),
|
|
706
|
+
).resolves.toBeUndefined();
|
|
707
|
+
|
|
708
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
709
|
+
'No repositories found in scan base directory: /repos',
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
errorSpy.mockRestore();
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('logs an error and continues scanning remaining repositories when osv-scanner exits with an unexpected code', async () => {
|
|
716
|
+
const { useCase, mockLocalCommandRunner, mockIssueRepository } =
|
|
717
|
+
buildUseCase();
|
|
718
|
+
const errorSpy = jest
|
|
719
|
+
.spyOn(console, 'error')
|
|
720
|
+
.mockImplementation(() => undefined);
|
|
721
|
+
|
|
722
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
723
|
+
|
|
724
|
+
mockLocalCommandRunner.runCommand.mockImplementation(
|
|
725
|
+
async (program, args) => {
|
|
726
|
+
if (program === 'find') {
|
|
727
|
+
return {
|
|
728
|
+
stdout:
|
|
729
|
+
'/repos/example-org/broken/.git\n/repos/example-org/app/.git\n',
|
|
730
|
+
stderr: '',
|
|
731
|
+
exitCode: 0,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
if (program === 'git') {
|
|
735
|
+
const dir = args[1];
|
|
736
|
+
const repo = dir.split('/').pop() ?? '';
|
|
737
|
+
return {
|
|
738
|
+
stdout: `git@github.com:example-org/${repo}.git\n`,
|
|
739
|
+
stderr: '',
|
|
740
|
+
exitCode: 0,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
if (program === 'osv-scanner') {
|
|
744
|
+
const dir = args[args.length - 1];
|
|
745
|
+
if (dir.includes('broken')) {
|
|
746
|
+
return {
|
|
747
|
+
stdout: '',
|
|
748
|
+
stderr: 'osv-scanner: command not found',
|
|
749
|
+
exitCode: 127,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return { stdout: 'vulnerability found', stderr: '', exitCode: 1 };
|
|
753
|
+
}
|
|
754
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
755
|
+
},
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
await useCase.run({
|
|
759
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
760
|
+
org: 'example-org',
|
|
761
|
+
manager: 'manager-name',
|
|
762
|
+
dailySecurityScan: {
|
|
763
|
+
scanBaseDirectory: '/repos',
|
|
764
|
+
targetHourUtc: 5,
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
769
|
+
expect.stringContaining('osv-scanner failed with exit code 127'),
|
|
770
|
+
);
|
|
771
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
|
|
772
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][1]).toBe('app');
|
|
773
|
+
errorSpy.mockRestore();
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('adds a comment to the existing open issue for the repository instead of creating a new one', async () => {
|
|
777
|
+
const { useCase, mockLocalCommandRunner, mockIssueRepository } =
|
|
778
|
+
buildUseCase();
|
|
779
|
+
|
|
780
|
+
mockIssueRepository.searchIssue.mockResolvedValue([
|
|
781
|
+
{
|
|
782
|
+
url: 'https://github.com/example-org/app/issues/42',
|
|
783
|
+
title: 'Daily security scan findings',
|
|
784
|
+
number: '42',
|
|
785
|
+
},
|
|
786
|
+
]);
|
|
787
|
+
|
|
788
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
789
|
+
if (program === 'find') {
|
|
790
|
+
return {
|
|
791
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
792
|
+
stderr: '',
|
|
793
|
+
exitCode: 0,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
if (program === 'git') {
|
|
797
|
+
return {
|
|
798
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
799
|
+
stderr: '',
|
|
800
|
+
exitCode: 0,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (program === 'osv-scanner') {
|
|
804
|
+
return { stdout: 'vulnerability found', stderr: '', exitCode: 1 };
|
|
805
|
+
}
|
|
806
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
await useCase.run({
|
|
810
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
811
|
+
org: 'example-org',
|
|
812
|
+
manager: 'manager-name',
|
|
813
|
+
dailySecurityScan: {
|
|
814
|
+
scanBaseDirectory: '/repos',
|
|
815
|
+
targetHourUtc: 5,
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(0);
|
|
820
|
+
expect(mockIssueRepository.createCommentByUrl.mock.calls).toHaveLength(1);
|
|
821
|
+
expect(mockIssueRepository.createCommentByUrl.mock.calls[0][0]).toBe(
|
|
822
|
+
'https://github.com/example-org/app/issues/42',
|
|
823
|
+
);
|
|
824
|
+
expect(mockIssueRepository.createCommentByUrl.mock.calls[0][1]).toContain(
|
|
825
|
+
'2024-01-02',
|
|
826
|
+
);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it('creates a new issue when no existing open issue exists for the repository', async () => {
|
|
830
|
+
const { useCase, mockLocalCommandRunner, mockIssueRepository } =
|
|
831
|
+
buildUseCase();
|
|
832
|
+
|
|
833
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
834
|
+
|
|
835
|
+
mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
|
|
836
|
+
if (program === 'find') {
|
|
837
|
+
return {
|
|
838
|
+
stdout: '/repos/example-org/app/.git\n',
|
|
839
|
+
stderr: '',
|
|
840
|
+
exitCode: 0,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (program === 'git') {
|
|
844
|
+
return {
|
|
845
|
+
stdout: 'git@github.com:example-org/app.git\n',
|
|
846
|
+
stderr: '',
|
|
847
|
+
exitCode: 0,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (program === 'osv-scanner') {
|
|
851
|
+
return { stdout: 'vulnerability found', stderr: '', exitCode: 1 };
|
|
852
|
+
}
|
|
853
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
await useCase.run({
|
|
857
|
+
targetDates: [new Date('2024-01-02T05:00:00Z')],
|
|
858
|
+
org: 'example-org',
|
|
859
|
+
manager: 'manager-name',
|
|
860
|
+
dailySecurityScan: {
|
|
861
|
+
scanBaseDirectory: '/repos',
|
|
862
|
+
targetHourUtc: 5,
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
|
|
867
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][2]).toBe(
|
|
868
|
+
'Daily security scan findings',
|
|
869
|
+
);
|
|
870
|
+
expect(mockIssueRepository.createCommentByUrl.mock.calls).toHaveLength(0);
|
|
871
|
+
});
|
|
632
872
|
});
|
|
633
873
|
});
|
|
@@ -53,7 +53,10 @@ const KEV_CATALOG_URL =
|
|
|
53
53
|
export class DailySecurityScanUseCase {
|
|
54
54
|
constructor(
|
|
55
55
|
readonly localCommandRunner: LocalCommandRunner,
|
|
56
|
-
readonly issueRepository: Pick<
|
|
56
|
+
readonly issueRepository: Pick<
|
|
57
|
+
IssueRepository,
|
|
58
|
+
'createNewIssue' | 'searchIssue' | 'createCommentByUrl'
|
|
59
|
+
>,
|
|
57
60
|
readonly httpRepository: HttpRepository,
|
|
58
61
|
) {}
|
|
59
62
|
|
|
@@ -100,10 +103,8 @@ export class DailySecurityScanUseCase {
|
|
|
100
103
|
'find',
|
|
101
104
|
[
|
|
102
105
|
config.scanBaseDirectory,
|
|
103
|
-
'-mindepth',
|
|
104
|
-
'4',
|
|
105
106
|
'-maxdepth',
|
|
106
|
-
'
|
|
107
|
+
'5',
|
|
107
108
|
'-name',
|
|
108
109
|
'.git',
|
|
109
110
|
'-type',
|
|
@@ -116,6 +117,13 @@ export class DailySecurityScanUseCase {
|
|
|
116
117
|
.filter((line) => line.length > 0)
|
|
117
118
|
.map((gitDirectory) => gitDirectory.replace(/\/\.git$/, ''));
|
|
118
119
|
|
|
120
|
+
if (repositoryDirectories.length === 0) {
|
|
121
|
+
console.error(
|
|
122
|
+
`No repositories found in scan base directory: ${config.scanBaseDirectory}`,
|
|
123
|
+
);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
for (const repositoryDirectory of repositoryDirectories) {
|
|
120
128
|
const { stdout: remoteUrl, exitCode: remoteExitCode } =
|
|
121
129
|
await this.localCommandRunner.runCommand('git', [
|
|
@@ -138,25 +146,52 @@ export class DailySecurityScanUseCase {
|
|
|
138
146
|
const repositoryOrg = remoteMatch[1];
|
|
139
147
|
const repositoryName = remoteMatch[2];
|
|
140
148
|
|
|
141
|
-
const {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
const {
|
|
150
|
+
stdout: scanOutput,
|
|
151
|
+
stderr: scanStderr,
|
|
152
|
+
exitCode: scanExitCode,
|
|
153
|
+
} = await this.localCommandRunner.runCommand('osv-scanner', [
|
|
154
|
+
'scan',
|
|
155
|
+
'source',
|
|
156
|
+
'-r',
|
|
157
|
+
repositoryDirectory,
|
|
158
|
+
]);
|
|
159
|
+
if (scanExitCode === 0) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
148
162
|
if (scanExitCode !== 1) {
|
|
163
|
+
console.error(
|
|
164
|
+
`osv-scanner failed with exit code ${scanExitCode} for ${repositoryDirectory}: ${scanStderr}`,
|
|
165
|
+
);
|
|
149
166
|
continue;
|
|
150
167
|
}
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
const findingsBody = `## OSV-Scanner findings\n\n### ${today}\n\n\`\`\`\n${scanOutput}\n\`\`\``;
|
|
170
|
+
const existingIssues = await this.issueRepository.searchIssue({
|
|
171
|
+
owner: repositoryOrg,
|
|
154
172
|
repositoryName,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
type: 'issue',
|
|
174
|
+
state: 'open',
|
|
175
|
+
title: 'Daily security scan findings',
|
|
176
|
+
});
|
|
177
|
+
const existingIssue = existingIssues.find(
|
|
178
|
+
(issue) => issue.title === 'Daily security scan findings',
|
|
159
179
|
);
|
|
180
|
+
if (existingIssue) {
|
|
181
|
+
await this.issueRepository.createCommentByUrl(
|
|
182
|
+
existingIssue.url,
|
|
183
|
+
findingsBody,
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
await this.issueRepository.createNewIssue(
|
|
187
|
+
repositoryOrg,
|
|
188
|
+
repositoryName,
|
|
189
|
+
'Daily security scan findings',
|
|
190
|
+
findingsBody,
|
|
191
|
+
[manager],
|
|
192
|
+
[],
|
|
193
|
+
);
|
|
194
|
+
}
|
|
160
195
|
}
|
|
161
196
|
};
|
|
162
197
|
|
|
@@ -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;
|
|
@@ -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;AA0CF,qBAAa,wBAAwB;IAEjC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB;IAC/C,QAAQ,CAAC,eAAe,EAAE,IAAI,
|
|
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,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,CA0Bf;IAEF,OAAO,CAAC,gBAAgB,CAoGtB;IAEF,OAAO,CAAC,kBAAkB,CAqDxB;IAEF,OAAO,CAAC,kCAAkC,CAsCxC;CACH"}
|