sec-report-kit 0.2.6__tar.gz → 0.2.7__tar.gz

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 (97) hide show
  1. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/CHANGELOG.md +8 -0
  2. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/PKG-INFO +1 -1
  3. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/pyproject.toml +1 -1
  4. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/requirements.txt +1 -1
  5. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/__init__.py +1 -1
  6. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/tests/test_cli.py +72 -1
  7. sec_report_kit-0.2.7/tests/test_html_renderer.py +166 -0
  8. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/tests/test_parsers.py +73 -0
  9. sec_report_kit-0.2.6/tests/test_html_renderer.py +0 -83
  10. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.gitattributes +0 -0
  11. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.github/workflows/pip-audit.yml +0 -0
  12. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.github/workflows/publish-pypi.yml +0 -0
  13. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.github/workflows/publish-testpypi.yml +0 -0
  14. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.github/workflows/trivy-fs-audit.yml +0 -0
  15. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.github/workflows/unit-test-coverage-gate.yml +0 -0
  16. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/.gitignore +0 -0
  17. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/README.md +0 -0
  18. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/bandit-gaps-vs-sast-open-source-tools.md +0 -0
  19. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/check_assets.py +0 -0
  20. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/download_safety_db.bat +0 -0
  21. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/download_safety_db.py +0 -0
  22. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/download_safety_db.sh +0 -0
  23. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/install_external_clis.py +0 -0
  24. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/install_tools.bat +0 -0
  25. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/install_tools.sh +0 -0
  26. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_bandit_html.bat +0 -0
  27. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_bandit_html.sh +0 -0
  28. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_checkov_html.bat +0 -0
  29. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_checkov_html.sh +0 -0
  30. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_codeql_html.bat +0 -0
  31. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_codeql_html.sh +0 -0
  32. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_consolidated_html.bat +0 -0
  33. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_consolidated_html.sh +0 -0
  34. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_gitleaks_html.bat +0 -0
  35. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_gitleaks_html.sh +0 -0
  36. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_osv_scanner_html.bat +0 -0
  37. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_osv_scanner_html.sh +0 -0
  38. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_pip_audit_html.bat +0 -0
  39. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_pip_audit_html.sh +0 -0
  40. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_safety_html.bat +0 -0
  41. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_safety_html.sh +0 -0
  42. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_semgrep_html.bat +0 -0
  43. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_semgrep_html.sh +0 -0
  44. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_tfsec_html.bat +0 -0
  45. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_tfsec_html.sh +0 -0
  46. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_trivy_html.bat +0 -0
  47. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_trivy_html.sh +0 -0
  48. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_trufflehog_html.bat +0 -0
  49. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/render_trufflehog_html.sh +0 -0
  50. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_bandit.bat +0 -0
  51. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_bandit.sh +0 -0
  52. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_checkov.bat +0 -0
  53. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_checkov.sh +0 -0
  54. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_codeql.bat +0 -0
  55. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_codeql.sh +0 -0
  56. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_gitleaks.bat +0 -0
  57. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_gitleaks.sh +0 -0
  58. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_osv_scanner.bat +0 -0
  59. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_osv_scanner.sh +0 -0
  60. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_pip_audit.bat +0 -0
  61. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_pip_audit.sh +0 -0
  62. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_safety.bat +0 -0
  63. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_safety.sh +0 -0
  64. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_semgrep.bat +0 -0
  65. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_semgrep.sh +0 -0
  66. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_tfsec.bat +0 -0
  67. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_tfsec.sh +0 -0
  68. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_trivy.bat +0 -0
  69. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_trivy.sh +0 -0
  70. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_trufflehog.bat +0 -0
  71. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_trufflehog.sh +0 -0
  72. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_unit_tests_with_coverage.bat +0 -0
  73. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/scripts/run_unit_tests_with_coverage.sh +0 -0
  74. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/__main__.py +0 -0
  75. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/cli.py +0 -0
  76. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/generate_consolidated_security_report.py +0 -0
  77. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/mcp/__init__.py +0 -0
  78. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/mcp/server.py +0 -0
  79. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/models.py +0 -0
  80. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/__init__.py +0 -0
  81. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/bandit.py +0 -0
  82. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/checkov.py +0 -0
  83. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/codeql.py +0 -0
  84. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/gitleaks.py +0 -0
  85. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/osv_scanner.py +0 -0
  86. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/pip_audit.py +0 -0
  87. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/safety.py +0 -0
  88. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/semgrep.py +0 -0
  89. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/tfsec.py +0 -0
  90. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/trivy.py +0 -0
  91. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/parsers/trufflehog.py +0 -0
  92. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/report/__init__.py +0 -0
  93. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/report/html_renderer.py +0 -0
  94. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/services/__init__.py +0 -0
  95. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/services/normalize.py +0 -0
  96. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/src/sec_report_kit/services/summarize.py +0 -0
  97. {sec_report_kit-0.2.6 → sec_report_kit-0.2.7}/tests/test_mcp_server.py +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.2.7 - 2026-05-17
6
+
7
+ ### Added
8
+ - Test coverage now includes the consolidated date-filter helpers, Safety parser fallback branches, and consolidated HTML dashboard visibility paths, bringing enforced suite coverage to 100%.
9
+
10
+ ### Fixed
11
+ - `requirements.txt` now pins `urllib3==2.7.0` to resolve the Trivy-reported CVEs affecting `2.6.3`.
12
+
5
13
  ## 0.2.6 - 2026-05-17
6
14
 
7
15
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sec-report-kit
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Generate HTML security reports from Trivy, pip-audit, Safety, Bandit, and Gitleaks JSON
5
5
  Author: Ravi Konduru
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sec-report-kit"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Generate HTML security reports from Trivy, pip-audit, Safety, Bandit, and Gitleaks JSON"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -80,5 +80,5 @@ twine==6.2.0
80
80
  typer==0.25.1
81
81
  typing-inspection==0.4.2
82
82
  typing_extensions==4.15.0
83
- urllib3==2.6.3
83
+ urllib3==2.7.0
84
84
  uvicorn==0.46.0
@@ -1,4 +1,4 @@
1
1
  """sec_report_kit package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.2.6"
4
+ __version__ = "0.2.7"
@@ -8,7 +8,7 @@ from unittest.mock import patch
8
8
  import pytest
9
9
  from typer.testing import CliRunner
10
10
 
11
- from sec_report_kit.cli import _write_report, app
11
+ from sec_report_kit.cli import _parse_modified_since, _parse_modified_until, _write_report, app
12
12
 
13
13
  runner = CliRunner()
14
14
 
@@ -666,6 +666,77 @@ def test_generate_consolidated_wrapper_invokes_cli(monkeypatch, tmp_path):
666
666
  )
667
667
 
668
668
 
669
+ def test_parse_modified_since_supports_named_ranges():
670
+ today = _parse_modified_since("today")
671
+ yesterday = _parse_modified_since("yesterday")
672
+ last_week = _parse_modified_since("last-week")
673
+
674
+ assert today.hour == 0
675
+ assert today.minute == 0
676
+ assert yesterday.hour == 0
677
+ assert yesterday.minute == 0
678
+ assert yesterday < today
679
+ assert last_week < today
680
+
681
+
682
+ def test_parse_modified_since_supports_iso_inputs_and_raises_for_invalid():
683
+ naive = _parse_modified_since("2026-05-10")
684
+ aware = _parse_modified_since("2026-05-10T09:00:00Z")
685
+
686
+ assert naive.year == 2026
687
+ assert naive.month == 5
688
+ assert naive.day == 10
689
+ assert naive.tzinfo is not None
690
+ assert aware.tzinfo is not None
691
+
692
+ import typer
693
+
694
+ with pytest.raises(typer.BadParameter):
695
+ _parse_modified_since("not-a-date")
696
+
697
+
698
+ def test_parse_modified_until_supports_named_and_date_only_inputs():
699
+ today = _parse_modified_until("today")
700
+ yesterday = _parse_modified_until("yesterday")
701
+ date_only = _parse_modified_until("2026-05-10")
702
+ datetime_value = _parse_modified_until("2026-05-10T09:15:00")
703
+
704
+ assert today.hour == 23
705
+ assert today.minute == 59
706
+ assert today.second == 59
707
+ assert today.microsecond == 999999
708
+ assert yesterday.hour == 23
709
+ assert yesterday.minute == 59
710
+ assert date_only.hour == 23
711
+ assert date_only.minute == 59
712
+ assert datetime_value.hour == 9
713
+ assert datetime_value.minute == 15
714
+
715
+
716
+ def test_generate_consolidated_wrapper_module_main(monkeypatch, tmp_path):
717
+ input_dir = tmp_path / "reports"
718
+ output_dir = tmp_path / "out"
719
+ input_dir.mkdir()
720
+
721
+ monkeypatch.setattr(
722
+ "sys.argv",
723
+ [
724
+ "generate_consolidated_security_report.py",
725
+ "--input",
726
+ str(input_dir),
727
+ "--output",
728
+ str(output_dir),
729
+ ],
730
+ )
731
+
732
+ with patch("sec_report_kit.cli.render_consolidated") as mock_render:
733
+ with pytest.raises(SystemExit) as exc:
734
+ runpy.run_module("sec_report_kit.generate_consolidated_security_report", run_name="__main__")
735
+
736
+ assert exc.value.code == 0
737
+ mock_render.assert_called_once()
738
+
739
+
669
740
  def test_consolidated_helper_scripts_exist():
670
741
  assert Path("scripts/render_consolidated_html.bat").exists()
671
742
  assert Path("scripts/render_consolidated_html.sh").exists()
@@ -0,0 +1,166 @@
1
+ from sec_report_kit.models import Finding
2
+ from sec_report_kit.report.html_renderer import render_consolidated_dashboard_report, render_html_report
3
+
4
+
5
+ EMPTY_COUNTS = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "UNKNOWN": 0}
6
+
7
+
8
+ def _finding(severity="HIGH", primary_url="https://example.com"):
9
+ return Finding(
10
+ source_type="test",
11
+ target="myimage:1",
12
+ severity=severity,
13
+ vulnerability_id="CVE-2024-0001",
14
+ package="openssl",
15
+ installed_version="1.0",
16
+ fixed_version="1.1",
17
+ title="Test vuln",
18
+ primary_url=primary_url,
19
+ )
20
+
21
+
22
+ def test_render_html_with_finding_with_url():
23
+ counts = {**EMPTY_COUNTS, "HIGH": 1}
24
+ html = render_html_report(
25
+ target_ref="myimage:1",
26
+ source_label="trivy",
27
+ findings=[_finding(primary_url="https://example.com")],
28
+ counts=counts,
29
+ )
30
+ assert "CVE-2024-0001" in html
31
+ assert "https://example.com" in html
32
+ assert "openssl" in html
33
+ assert "This report is generated by" in html
34
+ assert ">sec-report-kit<" in html
35
+ assert "Connect with the developer:" in html
36
+ assert ">Shan Konduru<" in html
37
+ assert "https://www.linkedin.com/in/shankonduru/" in html
38
+
39
+
40
+ def test_render_html_with_finding_no_url():
41
+ counts = {**EMPTY_COUNTS, "HIGH": 1}
42
+ html = render_html_report(
43
+ target_ref="myimage:1",
44
+ source_label="trivy",
45
+ findings=[_finding(primary_url="")],
46
+ counts=counts,
47
+ )
48
+ assert "CVE-2024-0001" in html
49
+ # no anchor tag when url is empty
50
+ assert 'href=""' not in html
51
+
52
+
53
+ def test_render_html_no_findings():
54
+ html = render_html_report(
55
+ target_ref="myimage:1",
56
+ source_label="trivy",
57
+ findings=[],
58
+ counts=EMPTY_COUNTS,
59
+ )
60
+ assert "No vulnerabilities found" in html
61
+
62
+
63
+ def test_render_html_escapes_special_chars():
64
+ finding = Finding(
65
+ source_type="test",
66
+ target='target<script>alert(1)</script>',
67
+ severity="LOW",
68
+ vulnerability_id="CVE-X",
69
+ package='pkg"quoted"',
70
+ installed_version="1.0",
71
+ fixed_version="-",
72
+ title="<b>bold</b>",
73
+ primary_url="",
74
+ )
75
+ counts = {**EMPTY_COUNTS, "LOW": 1}
76
+ html = render_html_report(
77
+ target_ref="ref",
78
+ source_label="test",
79
+ findings=[finding],
80
+ counts=counts,
81
+ )
82
+ assert "<script>alert(1)</script>" not in html
83
+ assert "&lt;script&gt;alert(1)&lt;/script&gt;" in html
84
+
85
+
86
+ def test_render_consolidated_dashboard_with_available_and_missing_reports(tmp_path):
87
+ reports_dir = tmp_path / "reports"
88
+ output_dir = tmp_path / "out"
89
+ reports_dir.mkdir()
90
+ output_dir.mkdir()
91
+
92
+ (reports_dir / "bandit-report.html").write_text("<html>bandit</html>", encoding="utf-8")
93
+ (output_dir / "trivy-report.html").write_text("<html>trivy</html>", encoding="utf-8")
94
+
95
+ findings = [
96
+ Finding(
97
+ source_type="bandit",
98
+ target="src/app.py",
99
+ severity="HIGH",
100
+ vulnerability_id="B101",
101
+ package="-",
102
+ installed_version="-",
103
+ fixed_version="-",
104
+ title="Assert used",
105
+ primary_url="",
106
+ ),
107
+ Finding(
108
+ source_type="trivy",
109
+ target="requirements.txt",
110
+ severity="CRITICAL",
111
+ vulnerability_id="CVE-1",
112
+ package="urllib3",
113
+ installed_version="2.6.3",
114
+ fixed_version="2.7.0",
115
+ title="Critical vuln",
116
+ primary_url="",
117
+ ),
118
+ Finding(
119
+ source_type="pip-audit",
120
+ target="requirements.txt",
121
+ severity="MEDIUM",
122
+ vulnerability_id="GHSA-1",
123
+ package="requests",
124
+ installed_version="2.0.0",
125
+ fixed_version="2.1.0",
126
+ title="Dependency finding",
127
+ primary_url="",
128
+ ),
129
+ ]
130
+
131
+ html = render_consolidated_dashboard_report(
132
+ target_ref="repo-root",
133
+ findings=findings,
134
+ counts={"CRITICAL": 1, "HIGH": 1, "MEDIUM": 1, "LOW": 0, "UNKNOWN": 0},
135
+ reports_dir=reports_dir,
136
+ output_dir=output_dir,
137
+ )
138
+
139
+ assert 'href="../reports/bandit-report.html"' in html
140
+ assert 'src="trivy-report.html"' in html
141
+ assert "Open Full Report" in html
142
+ assert "Generate this report first using scripts/render_pip_audit_html.sh or scripts/render_pip_audit_html.bat." in html
143
+ assert "scripts/render_pip_audit_html.sh" in html
144
+ assert "Bandit Report" in html
145
+ assert "Trivy Report" in html
146
+ assert "Reports available:" in html
147
+
148
+
149
+ def test_render_consolidated_dashboard_shows_all_tools_when_no_visible_tools(tmp_path):
150
+ reports_dir = tmp_path / "reports"
151
+ output_dir = tmp_path / "out"
152
+ reports_dir.mkdir()
153
+ output_dir.mkdir()
154
+
155
+ html = render_consolidated_dashboard_report(
156
+ target_ref="repo-root",
157
+ findings=[],
158
+ counts={"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "UNKNOWN": 0},
159
+ reports_dir=reports_dir,
160
+ output_dir=output_dir,
161
+ )
162
+
163
+ assert "pip-audit Report" in html
164
+ assert "GitLeaks Report" in html
165
+ assert "TruffleHog Report" in html
166
+ assert "Generate this report first using scripts/render_trivy_html.sh or scripts/render_trivy_html.bat." in html
@@ -88,6 +88,79 @@ def test_parse_safety_json_basic():
88
88
  assert findings[0].fixed_version == "1.26.19"
89
89
 
90
90
 
91
+ def test_parse_safety_json_list_payload_and_helper_fallbacks():
92
+ payload = [
93
+ {
94
+ "id": "CVE-2026-1",
95
+ "package": "requests",
96
+ "installed_version": "2.0.0",
97
+ "fixed_version": "2.1.0",
98
+ "description": "Fallback description",
99
+ "url": "https://example.com/safety/url",
100
+ },
101
+ {
102
+ "CVE": "CVE-2026-2",
103
+ "package_name": "urllib3",
104
+ "analyzed_version": "1.0.0",
105
+ "fix_versions": ["1.1.0", "1.2.0"],
106
+ "summary": "Summary title",
107
+ "references": [{"url": "https://example.com/safety/ref"}],
108
+ "severity": {"name": "medium"},
109
+ },
110
+ {
111
+ "vulnerability_id": "CVE-2026-3",
112
+ "package_name": "idna",
113
+ "analyzed_version": "3.0",
114
+ "fixed_versions": [],
115
+ "references": ["https://example.com/safety/list-ref"],
116
+ "severity": {"value": "low"},
117
+ },
118
+ ]
119
+
120
+ findings = parse_safety_json(payload)
121
+
122
+ assert len(findings) == 3
123
+ assert findings[0].severity == "UNKNOWN"
124
+ assert findings[0].fixed_version == "2.1.0"
125
+ assert findings[0].title == "Fallback description"
126
+ assert findings[0].primary_url == "https://example.com/safety/url"
127
+ assert findings[1].severity == "MEDIUM"
128
+ assert findings[1].vulnerability_id == "CVE-2026-2"
129
+ assert findings[1].fixed_version == "1.1.0, 1.2.0"
130
+ assert findings[1].title == "Summary title"
131
+ assert findings[1].primary_url == "https://example.com/safety/ref"
132
+ assert findings[2].severity == "LOW"
133
+ assert findings[2].fixed_version == "-"
134
+ assert findings[2].title == "Safety finding"
135
+ assert findings[2].primary_url == "https://example.com/safety/list-ref"
136
+
137
+
138
+ def test_parse_safety_json_dict_filters_non_dict_items_and_handles_unknown_severity():
139
+ payload = {
140
+ "vulnerabilities": [
141
+ "skip-me",
142
+ {"package_name": "pkg", "severity": {"other": "none"}},
143
+ ]
144
+ }
145
+
146
+ findings = parse_safety_json(payload)
147
+
148
+ assert len(findings) == 1
149
+ assert findings[0].severity == "UNKNOWN"
150
+ assert findings[0].vulnerability_id == "-"
151
+ assert findings[0].package == "pkg"
152
+
153
+
154
+ def test_detect_source_type_safety_from_scanned_packages_metadata():
155
+ payload = {"scanned_packages": [], "vulnerabilities": []}
156
+ assert detect_source_type(payload) == "safety"
157
+
158
+
159
+ def test_detect_source_type_safety_from_cve_sample():
160
+ payload = {"vulnerabilities": [{"CVE": "CVE-2026-1000"}]}
161
+ assert detect_source_type(payload) == "safety"
162
+
163
+
91
164
  def test_count_by_severity_unknown_when_missing():
92
165
  payload = {
93
166
  "dependencies": [
@@ -1,83 +0,0 @@
1
- from sec_report_kit.models import Finding
2
- from sec_report_kit.report.html_renderer import render_html_report
3
-
4
-
5
- EMPTY_COUNTS = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "UNKNOWN": 0}
6
-
7
-
8
- def _finding(severity="HIGH", primary_url="https://example.com"):
9
- return Finding(
10
- source_type="test",
11
- target="myimage:1",
12
- severity=severity,
13
- vulnerability_id="CVE-2024-0001",
14
- package="openssl",
15
- installed_version="1.0",
16
- fixed_version="1.1",
17
- title="Test vuln",
18
- primary_url=primary_url,
19
- )
20
-
21
-
22
- def test_render_html_with_finding_with_url():
23
- counts = {**EMPTY_COUNTS, "HIGH": 1}
24
- html = render_html_report(
25
- target_ref="myimage:1",
26
- source_label="trivy",
27
- findings=[_finding(primary_url="https://example.com")],
28
- counts=counts,
29
- )
30
- assert "CVE-2024-0001" in html
31
- assert "https://example.com" in html
32
- assert "openssl" in html
33
- assert "This report is generated by" in html
34
- assert ">sec-report-kit<" in html
35
- assert "Connect with the developer:" in html
36
- assert ">Shan Konduru<" in html
37
- assert "https://www.linkedin.com/in/shankonduru/" in html
38
-
39
-
40
- def test_render_html_with_finding_no_url():
41
- counts = {**EMPTY_COUNTS, "HIGH": 1}
42
- html = render_html_report(
43
- target_ref="myimage:1",
44
- source_label="trivy",
45
- findings=[_finding(primary_url="")],
46
- counts=counts,
47
- )
48
- assert "CVE-2024-0001" in html
49
- # no anchor tag when url is empty
50
- assert 'href=""' not in html
51
-
52
-
53
- def test_render_html_no_findings():
54
- html = render_html_report(
55
- target_ref="myimage:1",
56
- source_label="trivy",
57
- findings=[],
58
- counts=EMPTY_COUNTS,
59
- )
60
- assert "No vulnerabilities found" in html
61
-
62
-
63
- def test_render_html_escapes_special_chars():
64
- finding = Finding(
65
- source_type="test",
66
- target='target<script>alert(1)</script>',
67
- severity="LOW",
68
- vulnerability_id="CVE-X",
69
- package='pkg"quoted"',
70
- installed_version="1.0",
71
- fixed_version="-",
72
- title="<b>bold</b>",
73
- primary_url="",
74
- )
75
- counts = {**EMPTY_COUNTS, "LOW": 1}
76
- html = render_html_report(
77
- target_ref="ref",
78
- source_label="test",
79
- findings=[finding],
80
- counts=counts,
81
- )
82
- assert "<script>alert(1)</script>" not in html
83
- assert "&lt;script&gt;alert(1)&lt;/script&gt;" in html
File without changes