github-issue-tower-defence-management 1.148.6 → 1.148.8

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +5 -1
  3. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  4. package/bin/domain/usecases/DailySecurityScanUseCase.js +65 -41
  5. package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
  6. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +8 -1
  7. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  8. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +6 -0
  9. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  10. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +31 -0
  11. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +1 -0
  12. package/package.json +1 -1
  13. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +43 -0
  14. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +11 -1
  15. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +299 -237
  16. package/src/domain/usecases/DailySecurityScanUseCase.ts +106 -60
  17. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +79 -0
  18. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +13 -1
  19. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +81 -0
  20. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +13 -1
  21. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +114 -0
  22. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +34 -0
  23. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  24. package/types/domain/usecases/DailySecurityScanUseCase.d.ts +0 -1
  25. package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
  26. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  27. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -1
  28. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  29. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +5 -0
  30. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +1 -0
@@ -311,14 +311,53 @@ describe('DailySecurityScanUseCase', () => {
311
311
  mockHttpRepository,
312
312
  } = buildUseCase();
313
313
 
314
- mockLocalCommandRunner.runCommand.mockImplementation(
315
- async (program, args) => {
316
- if (program === 'find' && args.includes('-maxdepth')) {
317
- return { stdout: '/repos/app/.git\n', stderr: '', exitCode: 0 };
318
- }
319
- return { stdout: '', stderr: '', exitCode: 0 };
320
- },
321
- );
314
+ mockIssueRepository.searchIssue.mockResolvedValue([]);
315
+ mockLocalCommandRunner.runCommand.mockImplementation(async (program) => {
316
+ if (program === 'find') {
317
+ return {
318
+ stdout: '/repos/example-org/app/.git\n',
319
+ stderr: '',
320
+ exitCode: 0,
321
+ };
322
+ }
323
+ if (program === 'git') {
324
+ return {
325
+ stdout: 'git@github.com:example-org/app.git\n',
326
+ stderr: '',
327
+ exitCode: 0,
328
+ };
329
+ }
330
+ if (program === 'osv-scanner') {
331
+ return {
332
+ stdout: JSON.stringify({
333
+ results: [
334
+ {
335
+ source: { path: '/repos/example-org/app', type: 'lockfile' },
336
+ packages: [
337
+ {
338
+ package: {
339
+ name: 'example-library',
340
+ version: '1.2.3',
341
+ ecosystem: 'npm',
342
+ },
343
+ vulnerabilities: [
344
+ {
345
+ id: 'GHSA-1111-2222-3333',
346
+ aliases: ['CVE-2024-0001', 'CVE-2023-9999'],
347
+ summary: 'New Vulnerability',
348
+ },
349
+ ],
350
+ },
351
+ ],
352
+ },
353
+ ],
354
+ }),
355
+ stderr: '',
356
+ exitCode: 1,
357
+ };
358
+ }
359
+ return { stdout: '', stderr: '', exitCode: 0 };
360
+ });
322
361
  mockHttpRepository.get.mockResolvedValue(
323
362
  JSON.stringify({
324
363
  vulnerabilities: [
@@ -354,22 +393,17 @@ describe('DailySecurityScanUseCase', () => {
354
393
 
355
394
  expect(mockHttpRepository.get.mock.calls).toHaveLength(1);
356
395
  expect(mockHttpRepository.get.mock.calls[0][0]).toBe(KEV_CATALOG_URL);
357
- expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
358
- expect(mockIssueRepository.createNewIssue.mock.calls[0][0]).toBe(
359
- 'example-org',
360
- );
361
- expect(mockIssueRepository.createNewIssue.mock.calls[0][1]).toBe(
362
- 'security-reports',
363
- );
364
- expect(mockIssueRepository.createNewIssue.mock.calls[0][2]).toBe(
396
+ const kevIssueCalls =
397
+ mockIssueRepository.createNewIssue.mock.calls.filter(
398
+ (call) => call[1] === 'security-reports',
399
+ );
400
+ expect(kevIssueCalls).toHaveLength(1);
401
+ expect(kevIssueCalls[0][0]).toBe('example-org');
402
+ expect(kevIssueCalls[0][2]).toBe(
365
403
  'CISA KEV new additions since 2024-01-01',
366
404
  );
367
- expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).toContain(
368
- 'CVE-2024-0001',
369
- );
370
- expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).not.toContain(
371
- 'CVE-2023-9999',
372
- );
405
+ expect(kevIssueCalls[0][3]).toContain('CVE-2024-0001');
406
+ expect(kevIssueCalls[0][3]).not.toContain('CVE-2023-9999');
373
407
  });
374
408
 
375
409
  it('does not create a KEV report issue when there are no new additions', async () => {
@@ -428,218 +462,6 @@ describe('DailySecurityScanUseCase', () => {
428
462
  expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(0);
429
463
  });
430
464
 
431
- it('does not create a KEV report issue when the product is absent from the scanned workspace', async () => {
432
- const {
433
- useCase,
434
- mockLocalCommandRunner,
435
- mockIssueRepository,
436
- mockHttpRepository,
437
- } = buildUseCase();
438
-
439
- mockLocalCommandRunner.runCommand.mockImplementation(
440
- async (program, args) => {
441
- if (program === 'find' && args.includes('-maxdepth')) {
442
- return {
443
- stdout: '/repos/app/.git\n/repos/site/.git\n',
444
- stderr: '',
445
- exitCode: 0,
446
- };
447
- }
448
- if (program === 'git' && args.includes('grep')) {
449
- return { stdout: '', stderr: '', exitCode: 1 };
450
- }
451
- return { stdout: '', stderr: '', exitCode: 0 };
452
- },
453
- );
454
- mockHttpRepository.get.mockResolvedValue(
455
- JSON.stringify({
456
- vulnerabilities: [
457
- {
458
- cveID: 'CVE-2024-0001',
459
- vendorProject: 'Progress',
460
- product: 'LoadMaster',
461
- vulnerabilityName: 'Progress LoadMaster Command Injection',
462
- dateAdded: '2024-01-02',
463
- },
464
- ],
465
- }),
466
- );
467
-
468
- await useCase.run({
469
- targetDates: [new Date('2024-01-02T05:00:00Z')],
470
- org: 'example-org',
471
- manager: 'manager-name',
472
- dailySecurityScan: {
473
- scanBaseDirectory: '/repos',
474
- targetHourUtc: 5,
475
- enableKevNvdReport: true,
476
- kevReportRepo: 'security-reports',
477
- },
478
- });
479
-
480
- const gitGrepCalls = mockLocalCommandRunner.runCommand.mock.calls.filter(
481
- (call) => call[0] === 'git' && call[1].includes('grep'),
482
- );
483
- expect(gitGrepCalls.map((call) => call[1])).toEqual([
484
- [
485
- '-C',
486
- '/repos/app',
487
- 'grep',
488
- '-I',
489
- '-i',
490
- '-q',
491
- '-F',
492
- '-e',
493
- 'LoadMaster',
494
- ],
495
- [
496
- '-C',
497
- '/repos/site',
498
- 'grep',
499
- '-I',
500
- '-i',
501
- '-q',
502
- '-F',
503
- '-e',
504
- 'LoadMaster',
505
- ],
506
- ]);
507
- expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(0);
508
- });
509
-
510
- it('reports only the new KEV entries whose product is present in the scanned workspace', async () => {
511
- const {
512
- useCase,
513
- mockLocalCommandRunner,
514
- mockIssueRepository,
515
- mockHttpRepository,
516
- } = buildUseCase();
517
-
518
- mockLocalCommandRunner.runCommand.mockImplementation(
519
- async (program, args) => {
520
- if (program === 'find' && args.includes('-maxdepth')) {
521
- return { stdout: '/repos/app/.git\n', stderr: '', exitCode: 0 };
522
- }
523
- if (program === 'git' && args.includes('grep')) {
524
- return {
525
- stdout: '',
526
- stderr: '',
527
- exitCode: args.includes('LoadMaster') ? 1 : 0,
528
- };
529
- }
530
- return { stdout: '', stderr: '', exitCode: 0 };
531
- },
532
- );
533
- mockHttpRepository.get.mockResolvedValue(
534
- JSON.stringify({
535
- vulnerabilities: [
536
- {
537
- cveID: 'CVE-2024-0001',
538
- vendorProject: 'Progress',
539
- product: 'LoadMaster',
540
- vulnerabilityName: 'Progress LoadMaster Command Injection',
541
- dateAdded: '2024-01-02',
542
- },
543
- {
544
- cveID: 'CVE-2024-0002',
545
- vendorProject: 'Example',
546
- product: 'ExampleLibrary',
547
- vulnerabilityName: 'Example Library Deserialization',
548
- dateAdded: '2024-01-02',
549
- },
550
- ],
551
- }),
552
- );
553
-
554
- await useCase.run({
555
- targetDates: [new Date('2024-01-02T05:00:00Z')],
556
- org: 'example-org',
557
- manager: 'manager-name',
558
- dailySecurityScan: {
559
- scanBaseDirectory: '/repos',
560
- targetHourUtc: 5,
561
- enableKevNvdReport: true,
562
- kevReportRepo: 'security-reports',
563
- },
564
- });
565
-
566
- expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
567
- expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).toContain(
568
- 'CVE-2024-0002',
569
- );
570
- expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).not.toContain(
571
- 'CVE-2024-0001',
572
- );
573
- });
574
-
575
- it('logs a failed repository search and keeps searching the remaining repositories', async () => {
576
- const {
577
- useCase,
578
- mockLocalCommandRunner,
579
- mockIssueRepository,
580
- mockHttpRepository,
581
- } = buildUseCase();
582
- const errorSpy = jest
583
- .spyOn(console, 'error')
584
- .mockImplementation(() => undefined);
585
-
586
- mockLocalCommandRunner.runCommand.mockImplementation(
587
- async (program, args) => {
588
- if (program === 'find' && args.includes('-maxdepth')) {
589
- return {
590
- stdout: '/repos/broken/.git\n/repos/app/.git\n',
591
- stderr: '',
592
- exitCode: 0,
593
- };
594
- }
595
- if (program === 'git' && args.includes('grep')) {
596
- return args.includes('/repos/broken')
597
- ? {
598
- stdout: '',
599
- stderr: 'fatal: not a git repository',
600
- exitCode: 128,
601
- }
602
- : { stdout: '', stderr: '', exitCode: 0 };
603
- }
604
- return { stdout: '', stderr: '', exitCode: 0 };
605
- },
606
- );
607
- mockHttpRepository.get.mockResolvedValue(
608
- JSON.stringify({
609
- vulnerabilities: [
610
- {
611
- cveID: 'CVE-2024-0001',
612
- vendorProject: 'Progress',
613
- product: 'LoadMaster',
614
- vulnerabilityName: 'Progress LoadMaster Command Injection',
615
- dateAdded: '2024-01-02',
616
- },
617
- ],
618
- }),
619
- );
620
-
621
- await useCase.run({
622
- targetDates: [new Date('2024-01-02T05:00:00Z')],
623
- org: 'example-org',
624
- manager: 'manager-name',
625
- dailySecurityScan: {
626
- scanBaseDirectory: '/repos',
627
- targetHourUtc: 5,
628
- enableKevNvdReport: true,
629
- kevReportRepo: 'security-reports',
630
- },
631
- });
632
-
633
- expect(errorSpy).toHaveBeenCalledWith(
634
- 'Failed to search /repos/broken for LoadMaster: fatal: not a git repository',
635
- );
636
- expect(mockIssueRepository.createNewIssue.mock.calls).toHaveLength(1);
637
- expect(mockIssueRepository.createNewIssue.mock.calls[0][3]).toContain(
638
- 'CVE-2024-0001',
639
- );
640
- errorSpy.mockRestore();
641
- });
642
-
643
465
  it('throws when the KEV catalog format is unexpected', async () => {
644
466
  const { useCase, mockLocalCommandRunner, mockHttpRepository } =
645
467
  buildUseCase();
@@ -741,15 +563,41 @@ describe('DailySecurityScanUseCase', () => {
741
563
  };
742
564
  }
743
565
  if (program === 'osv-scanner') {
744
- const dir = args[args.length - 1];
745
- if (dir.includes('broken')) {
566
+ const scannedDirectory = args[args.indexOf('-r') + 1];
567
+ if (scannedDirectory.includes('broken')) {
746
568
  return {
747
569
  stdout: '',
748
570
  stderr: 'osv-scanner: command not found',
749
571
  exitCode: 127,
750
572
  };
751
573
  }
752
- return { stdout: 'vulnerability found', stderr: '', exitCode: 1 };
574
+ return {
575
+ stdout: JSON.stringify({
576
+ results: [
577
+ {
578
+ source: { path: scannedDirectory, type: 'lockfile' },
579
+ packages: [
580
+ {
581
+ package: {
582
+ name: 'example-library',
583
+ version: '1.2.3',
584
+ ecosystem: 'npm',
585
+ },
586
+ vulnerabilities: [
587
+ {
588
+ id: 'GHSA-1111-2222-3333',
589
+ aliases: [],
590
+ summary: 'Example Library Deserialization',
591
+ },
592
+ ],
593
+ },
594
+ ],
595
+ },
596
+ ],
597
+ }),
598
+ stderr: '',
599
+ exitCode: 1,
600
+ };
753
601
  }
754
602
  return { stdout: '', stderr: '', exitCode: 0 };
755
603
  },
@@ -869,5 +717,219 @@ describe('DailySecurityScanUseCase', () => {
869
717
  );
870
718
  expect(mockIssueRepository.createCommentByUrl.mock.calls).toHaveLength(0);
871
719
  });
720
+
721
+ const osvScanOutput = (
722
+ vulnerablePackages: {
723
+ name: string;
724
+ version: string;
725
+ ecosystem: string;
726
+ id: string;
727
+ aliases: string[];
728
+ summary: string;
729
+ }[],
730
+ ): string =>
731
+ JSON.stringify({
732
+ results: [
733
+ {
734
+ source: { path: '/repos/example-org/app', type: 'lockfile' },
735
+ packages: vulnerablePackages.map((vulnerablePackage) => ({
736
+ package: {
737
+ name: vulnerablePackage.name,
738
+ version: vulnerablePackage.version,
739
+ ecosystem: vulnerablePackage.ecosystem,
740
+ },
741
+ vulnerabilities: [
742
+ {
743
+ id: vulnerablePackage.id,
744
+ aliases: vulnerablePackage.aliases,
745
+ summary: vulnerablePackage.summary,
746
+ },
747
+ ],
748
+ })),
749
+ },
750
+ ],
751
+ });
752
+
753
+ const buildScanEnvironment = (scanOutput: string, kevCatalog: unknown) => {
754
+ const built = buildUseCase();
755
+ built.mockIssueRepository.searchIssue.mockResolvedValue([]);
756
+ built.mockLocalCommandRunner.runCommand.mockImplementation(
757
+ async (program) => {
758
+ if (program === 'find') {
759
+ return {
760
+ stdout: '/repos/example-org/app/.git\n',
761
+ stderr: '',
762
+ exitCode: 0,
763
+ };
764
+ }
765
+ if (program === 'git') {
766
+ return {
767
+ stdout: 'git@github.com:example-org/app.git\n',
768
+ stderr: '',
769
+ exitCode: 0,
770
+ };
771
+ }
772
+ if (program === 'osv-scanner') {
773
+ return { stdout: scanOutput, stderr: '', exitCode: 1 };
774
+ }
775
+ return { stdout: '', stderr: '', exitCode: 0 };
776
+ },
777
+ );
778
+ built.mockHttpRepository.get.mockResolvedValue(
779
+ JSON.stringify(kevCatalog),
780
+ );
781
+ return built;
782
+ };
783
+
784
+ const kevReportCalls = (
785
+ calls: [string, string, string, string, string[], string[]][],
786
+ ) => calls.filter((call) => call[1] === 'security-reports');
787
+
788
+ const runWithKevReporting = async (useCase: DailySecurityScanUseCase) =>
789
+ useCase.run({
790
+ targetDates: [new Date('2024-01-02T05:00:00Z')],
791
+ org: 'example-org',
792
+ manager: 'manager-name',
793
+ dailySecurityScan: {
794
+ scanBaseDirectory: '/repos',
795
+ targetHourUtc: 5,
796
+ enableKevNvdReport: true,
797
+ kevReportRepo: 'security-reports',
798
+ },
799
+ });
800
+
801
+ it('reports a KEV addition whose CVE the scanner found at an installed version', async () => {
802
+ const { useCase, mockIssueRepository } = buildScanEnvironment(
803
+ osvScanOutput([
804
+ {
805
+ name: 'example-library',
806
+ version: '1.2.3',
807
+ ecosystem: 'npm',
808
+ id: 'GHSA-1111-2222-3333',
809
+ aliases: ['CVE-2024-0001'],
810
+ summary: 'Example Library Deserialization',
811
+ },
812
+ ]),
813
+ {
814
+ vulnerabilities: [
815
+ {
816
+ cveID: 'CVE-2024-0001',
817
+ vendorProject: 'Example',
818
+ product: 'ExampleLibrary',
819
+ vulnerabilityName: 'Example Library Deserialization',
820
+ dateAdded: '2024-01-02',
821
+ },
822
+ ],
823
+ },
824
+ );
825
+
826
+ await runWithKevReporting(useCase);
827
+
828
+ const kevCalls = kevReportCalls(
829
+ mockIssueRepository.createNewIssue.mock.calls,
830
+ );
831
+ expect(kevCalls).toHaveLength(1);
832
+ expect(kevCalls[0][3]).toContain('CVE-2024-0001');
833
+ expect(kevCalls[0][3]).toContain('app');
834
+ expect(kevCalls[0][3]).toContain('example-library');
835
+ expect(kevCalls[0][3]).toContain('1.2.3');
836
+ });
837
+
838
+ it('does not report a KEV addition that the scanner did not find in any repository', async () => {
839
+ const { useCase, mockIssueRepository } = buildScanEnvironment(
840
+ osvScanOutput([
841
+ {
842
+ name: 'example-library',
843
+ version: '1.2.3',
844
+ ecosystem: 'npm',
845
+ id: 'GHSA-1111-2222-3333',
846
+ aliases: ['CVE-2024-0001'],
847
+ summary: 'Example Library Deserialization',
848
+ },
849
+ ]),
850
+ {
851
+ vulnerabilities: [
852
+ {
853
+ cveID: 'CVE-2024-9999',
854
+ vendorProject: 'Progress',
855
+ product: 'LoadMaster',
856
+ vulnerabilityName: 'Progress LoadMaster Command Injection',
857
+ dateAdded: '2024-01-02',
858
+ },
859
+ ],
860
+ },
861
+ );
862
+
863
+ await runWithKevReporting(useCase);
864
+
865
+ expect(
866
+ kevReportCalls(mockIssueRepository.createNewIssue.mock.calls),
867
+ ).toHaveLength(0);
868
+ });
869
+
870
+ it('reports a KEV addition whose CVE the scanner returned as the vulnerability id rather than an alias', async () => {
871
+ const { useCase, mockIssueRepository } = buildScanEnvironment(
872
+ osvScanOutput([
873
+ {
874
+ name: 'example-library',
875
+ version: '1.2.3',
876
+ ecosystem: 'npm',
877
+ id: 'CVE-2024-0001',
878
+ aliases: [],
879
+ summary: 'Example Library Deserialization',
880
+ },
881
+ ]),
882
+ {
883
+ vulnerabilities: [
884
+ {
885
+ cveID: 'CVE-2024-0001',
886
+ vendorProject: 'Example',
887
+ product: 'ExampleLibrary',
888
+ vulnerabilityName: 'Example Library Deserialization',
889
+ dateAdded: '2024-01-02',
890
+ },
891
+ ],
892
+ },
893
+ );
894
+
895
+ await runWithKevReporting(useCase);
896
+
897
+ expect(
898
+ kevReportCalls(mockIssueRepository.createNewIssue.mock.calls),
899
+ ).toHaveLength(1);
900
+ });
901
+
902
+ it('does not search the repositories for the KEV product name', async () => {
903
+ const { useCase, mockLocalCommandRunner } = buildScanEnvironment(
904
+ osvScanOutput([
905
+ {
906
+ name: 'example-library',
907
+ version: '1.2.3',
908
+ ecosystem: 'npm',
909
+ id: 'GHSA-1111-2222-3333',
910
+ aliases: ['CVE-2024-0001'],
911
+ summary: 'Example Library Deserialization',
912
+ },
913
+ ]),
914
+ {
915
+ vulnerabilities: [
916
+ {
917
+ cveID: 'CVE-2024-0001',
918
+ vendorProject: 'Example',
919
+ product: 'ExampleLibrary',
920
+ vulnerabilityName: 'Example Library Deserialization',
921
+ dateAdded: '2024-01-02',
922
+ },
923
+ ],
924
+ },
925
+ );
926
+
927
+ await runWithKevReporting(useCase);
928
+
929
+ const grepCalls = mockLocalCommandRunner.runCommand.mock.calls.filter(
930
+ (call) => call[0] === 'git' && call[1].includes('grep'),
931
+ );
932
+ expect(grepCalls).toHaveLength(0);
933
+ });
872
934
  });
873
935
  });