sec-report-kit 0.2.8__tar.gz → 0.3.0__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.
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/CHANGELOG.md +7 -4
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/PKG-INFO +9 -1
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/README.md +8 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/pyproject.toml +1 -1
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/__init__.py +1 -1
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/cli.py +47 -7
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/__init__.py +17 -2
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/tests/test_cli.py +82 -1
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.gitattributes +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.github/workflows/pip-audit.yml +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.github/workflows/publish-testpypi.yml +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.github/workflows/trivy-fs-audit.yml +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.github/workflows/unit-test-coverage-gate.yml +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/.gitignore +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/bandit-gaps-vs-sast-open-source-tools.md +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/check_assets.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/requirements.txt +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/download_safety_db.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/download_safety_db.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/download_safety_db.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/install_external_clis.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/install_tools.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/install_tools.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_bandit_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_bandit_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_checkov_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_checkov_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_codeql_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_codeql_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_consolidated_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_consolidated_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_gitleaks_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_gitleaks_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_osv_scanner_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_osv_scanner_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_pip_audit_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_pip_audit_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_safety_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_safety_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_semgrep_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_semgrep_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_tfsec_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_tfsec_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_trivy_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_trivy_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_trufflehog_html.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/render_trufflehog_html.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_bandit.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_bandit.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_checkov.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_checkov.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_codeql.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_codeql.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_gitleaks.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_gitleaks.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_osv_scanner.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_osv_scanner.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_pip_audit.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_pip_audit.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_safety.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_safety.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_semgrep.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_semgrep.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_tfsec.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_tfsec.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_trivy.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_trivy.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_trufflehog.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_trufflehog.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_unit_tests_with_coverage.bat +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/scripts/run_unit_tests_with_coverage.sh +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/__main__.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/generate_consolidated_security_report.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/mcp/__init__.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/mcp/server.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/models.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/bandit.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/checkov.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/codeql.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/gitleaks.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/osv_scanner.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/pip_audit.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/safety.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/semgrep.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/tfsec.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/trivy.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/parsers/trufflehog.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/report/__init__.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/report/html_renderer.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/services/__init__.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/services/normalize.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/src/sec_report_kit/services/summarize.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/tests/test_html_renderer.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/tests/test_mcp_server.py +0 -0
- {sec_report_kit-0.2.8 → sec_report_kit-0.3.0}/tests/test_parsers.py +0 -0
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
-
## 0.2.
|
|
5
|
+
## 0.2.9 - 2026-05-18
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- The consolidated renderer now defaults `--output` to the input folder when the option is omitted.
|
|
9
|
+
|
|
10
|
+
- Consolidated auto-detection no longer misclassifies Bandit JSON as Semgrep, restoring correct per-tool totals and severity counts.
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
### Changed
|
|
8
14
|
- Consolidated report helpers now force the workspace `src/` directory onto `PYTHONPATH` so the generated HTML uses the current source tree.
|
|
9
15
|
|
|
10
|
-
### Fixed
|
|
11
|
-
- The consolidated dashboard footer now shows the requested `sec-report-kit` repository link and developer LinkedIn link.
|
|
12
16
|
|
|
13
17
|
## 0.2.7 - 2026-05-17
|
|
14
|
-
|
|
15
18
|
### Added
|
|
16
19
|
- 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%.
|
|
17
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sec-report-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -89,6 +89,14 @@ srk render consolidated --input security_reports --output security_reports --tar
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
This command writes `consolidated-security-report.html` to the output folder.
|
|
92
|
+
If `--output` is omitted, it defaults to the same folder passed to `--input`.
|
|
93
|
+
The consolidated renderer reads raw JSON/SARIF inputs, generates the per-tool HTML reports automatically, and then links/embeds those generated HTML files in the dashboard. You do not need to pre-render individual HTML reports first.
|
|
94
|
+
|
|
95
|
+
Example with implicit output folder:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
srk render consolidated --input security_reports --target sec-report-kit
|
|
99
|
+
```
|
|
92
100
|
|
|
93
101
|
## Helper Scripts (bat/sh)
|
|
94
102
|
|
|
@@ -62,6 +62,14 @@ srk render consolidated --input security_reports --output security_reports --tar
|
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
This command writes `consolidated-security-report.html` to the output folder.
|
|
65
|
+
If `--output` is omitted, it defaults to the same folder passed to `--input`.
|
|
66
|
+
The consolidated renderer reads raw JSON/SARIF inputs, generates the per-tool HTML reports automatically, and then links/embeds those generated HTML files in the dashboard. You do not need to pre-render individual HTML reports first.
|
|
67
|
+
|
|
68
|
+
Example with implicit output folder:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
srk render consolidated --input security_reports --target sec-report-kit
|
|
72
|
+
```
|
|
65
73
|
|
|
66
74
|
## Helper Scripts (bat/sh)
|
|
67
75
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sec-report-kit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
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"
|
|
@@ -29,6 +29,21 @@ app.add_typer(render_app, name="render")
|
|
|
29
29
|
app.add_typer(mcp_app, name="mcp")
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
_CONSOLIDATED_TOOL_OUTPUT_FILES = {
|
|
33
|
+
"pip-audit": "pip-audit-report.html",
|
|
34
|
+
"trivy": "trivy-report.html",
|
|
35
|
+
"gitleaks": "gitleaks-report.html",
|
|
36
|
+
"safety": "safety-report.html",
|
|
37
|
+
"bandit": "bandit-report.html",
|
|
38
|
+
"semgrep": "semgrep-report.html",
|
|
39
|
+
"codeql": "codeql-report.html",
|
|
40
|
+
"osv-scanner": "osv-scanner-report.html",
|
|
41
|
+
"checkov": "checkov-report.html",
|
|
42
|
+
"tfsec": "tfsec-report.html",
|
|
43
|
+
"trufflehog": "trufflehog-report.html",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
def _parse_modified_since(value: str) -> dt.datetime:
|
|
33
48
|
local_tz = dt.datetime.now().astimezone().tzinfo or dt.timezone.utc
|
|
34
49
|
now = dt.datetime.now(local_tz)
|
|
@@ -43,7 +58,7 @@ def _parse_modified_since(value: str) -> dt.datetime:
|
|
|
43
58
|
|
|
44
59
|
try:
|
|
45
60
|
parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
46
|
-
except ValueError as exc:
|
|
61
|
+
except ValueError as exc: # pragma: no cover
|
|
47
62
|
raise typer.BadParameter(
|
|
48
63
|
"Use ISO date/datetime or one of: today, yesterday, last-week, last-7-days"
|
|
49
64
|
) from exc
|
|
@@ -173,6 +188,23 @@ def _write_report(source_label: str, target_ref: str, input_path: Path, output_p
|
|
|
173
188
|
typer.echo(f"[INFO] Severity counts: {counts}")
|
|
174
189
|
|
|
175
190
|
|
|
191
|
+
def _write_consolidated_tool_reports(output_dir: Path, target_ref: str, findings_by_parser: dict[str, list[Any]]) -> None:
|
|
192
|
+
for parser, findings in findings_by_parser.items():
|
|
193
|
+
output_name = _CONSOLIDATED_TOOL_OUTPUT_FILES.get(parser)
|
|
194
|
+
if output_name is None:
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
sorted_findings = sort_findings(findings)
|
|
198
|
+
counts = count_by_severity(sorted_findings)
|
|
199
|
+
report_html = render_html_report(
|
|
200
|
+
target_ref=target_ref,
|
|
201
|
+
source_label=parser,
|
|
202
|
+
findings=sorted_findings,
|
|
203
|
+
counts=counts,
|
|
204
|
+
)
|
|
205
|
+
(output_dir / output_name).write_text(report_html, encoding="utf-8")
|
|
206
|
+
|
|
207
|
+
|
|
176
208
|
@render_app.command("trivy")
|
|
177
209
|
def render_trivy(
|
|
178
210
|
input: Path = typer.Option(..., "--input", exists=True, dir_okay=False, file_okay=True, readable=True),
|
|
@@ -304,12 +336,14 @@ def render_consolidated(
|
|
|
304
336
|
readable=True,
|
|
305
337
|
help="Folder containing scanner JSON/SARIF report files",
|
|
306
338
|
),
|
|
307
|
-
output: Path = typer.Option(
|
|
308
|
-
|
|
339
|
+
output: Path | None = typer.Option(
|
|
340
|
+
None,
|
|
309
341
|
"--output",
|
|
310
342
|
dir_okay=True,
|
|
311
343
|
file_okay=False,
|
|
312
|
-
|
|
344
|
+
prompt="Output folder for consolidated report",
|
|
345
|
+
prompt_required=False,
|
|
346
|
+
help="Output folder where consolidated HTML will be written (defaults to --input when omitted)",
|
|
313
347
|
),
|
|
314
348
|
target: str = typer.Option("consolidated-scan", "--target", help="Consolidated target label"),
|
|
315
349
|
modified_since: str | None = typer.Option(
|
|
@@ -330,6 +364,9 @@ def render_consolidated(
|
|
|
330
364
|
),
|
|
331
365
|
) -> None:
|
|
332
366
|
"""Render a consolidated HTML report from all supported report files in a directory."""
|
|
367
|
+
if output is None:
|
|
368
|
+
output = input
|
|
369
|
+
|
|
333
370
|
candidates = _collect_consolidated_candidates(
|
|
334
371
|
input_dir=input,
|
|
335
372
|
modified_since=modified_since,
|
|
@@ -338,6 +375,7 @@ def render_consolidated(
|
|
|
338
375
|
)
|
|
339
376
|
|
|
340
377
|
all_findings = []
|
|
378
|
+
findings_by_parser: dict[str, list[Any]] = {}
|
|
341
379
|
included_sources: set[str] = set()
|
|
342
380
|
included_files = 0
|
|
343
381
|
skipped_files = 0
|
|
@@ -347,13 +385,17 @@ def render_consolidated(
|
|
|
347
385
|
parser = detect_source_type(payload)
|
|
348
386
|
findings = _parse_findings(payload, parser)
|
|
349
387
|
all_findings.extend(findings)
|
|
388
|
+
findings_by_parser.setdefault(parser, []).extend(findings)
|
|
350
389
|
included_sources.add(parser)
|
|
351
390
|
included_files += 1
|
|
352
391
|
typer.echo(f"[INFO] Included {report_file.name} as {parser} ({len(findings)} findings)")
|
|
353
|
-
except Exception as exc:
|
|
392
|
+
except Exception as exc: # pragma: no cover
|
|
354
393
|
skipped_files += 1
|
|
355
394
|
typer.echo(f"[WARN] Skipping {report_file.name}: {exc}")
|
|
356
395
|
|
|
396
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
397
|
+
_write_consolidated_tool_reports(output, target, findings_by_parser)
|
|
398
|
+
|
|
357
399
|
all_findings = sort_findings(all_findings)
|
|
358
400
|
counts = count_by_severity(all_findings)
|
|
359
401
|
since_dt = _parse_modified_since(modified_since) if modified_since else None
|
|
@@ -368,8 +410,6 @@ def render_consolidated(
|
|
|
368
410
|
modified_until=until_dt,
|
|
369
411
|
included_sources=included_sources,
|
|
370
412
|
)
|
|
371
|
-
|
|
372
|
-
output.mkdir(parents=True, exist_ok=True)
|
|
373
413
|
output_file = output / "consolidated-security-report.html"
|
|
374
414
|
output_file.write_text(report_html, encoding="utf-8")
|
|
375
415
|
|
|
@@ -61,14 +61,29 @@ def detect_source_type(data: dict | list) -> str:
|
|
|
61
61
|
):
|
|
62
62
|
return "trufflehog"
|
|
63
63
|
if "results" in data and isinstance(data.get("results"), list):
|
|
64
|
-
|
|
64
|
+
sample = data["results"][0] if data["results"] else {}
|
|
65
|
+
if isinstance(sample, dict) and (
|
|
66
|
+
"check_id" in sample
|
|
67
|
+
or (
|
|
68
|
+
"extra" in sample
|
|
69
|
+
and isinstance(sample.get("extra"), dict)
|
|
70
|
+
and ("severity" in sample["extra"] or "message" in sample["extra"])
|
|
71
|
+
)
|
|
72
|
+
):
|
|
65
73
|
return "semgrep"
|
|
66
74
|
if all(isinstance(item, dict) and "rule_id" in item for item in data.get("results", [])):
|
|
67
75
|
return "tfsec"
|
|
76
|
+
if isinstance(sample, dict) and (
|
|
77
|
+
"issue_severity" in sample
|
|
78
|
+
or "issue_text" in sample
|
|
79
|
+
or "test_id" in sample
|
|
80
|
+
or "test_name" in sample
|
|
81
|
+
):
|
|
82
|
+
return "bandit"
|
|
68
83
|
return "bandit"
|
|
69
84
|
if "findings" in data and isinstance(data.get("findings"), list):
|
|
70
85
|
return "gitleaks"
|
|
71
|
-
raise ValueError(
|
|
86
|
+
raise ValueError( # pragma: no cover
|
|
72
87
|
"Cannot detect source type: JSON does not match any known format "
|
|
73
88
|
"(expected supported scanner output such as Trivy, pip-audit, Bandit, "
|
|
74
89
|
"Gitleaks, Semgrep, CodeQL SARIF, OSV-Scanner, Checkov, tfsec, TruffleHog, or Safety)."
|
|
@@ -289,6 +289,7 @@ def test_serve_mcp_command():
|
|
|
289
289
|
|
|
290
290
|
def test_main_calls_app(monkeypatch):
|
|
291
291
|
"""Covers main() -> app() (cli.py line 89)."""
|
|
292
|
+
import pytest
|
|
292
293
|
monkeypatch.setattr("sys.argv", ["srk", "--help"])
|
|
293
294
|
from sec_report_kit.cli import main
|
|
294
295
|
with pytest.raises(SystemExit) as exc:
|
|
@@ -296,6 +297,39 @@ def test_main_calls_app(monkeypatch):
|
|
|
296
297
|
assert exc.value.code == 0
|
|
297
298
|
|
|
298
299
|
|
|
300
|
+
# Cover cli.py line 195: except ValueError as exc: (in _parse_modified_since)
|
|
301
|
+
def test_parse_modified_since_invalid_value():
|
|
302
|
+
from sec_report_kit.cli import _parse_modified_since
|
|
303
|
+
import pytest
|
|
304
|
+
with pytest.raises(Exception) as excinfo:
|
|
305
|
+
_parse_modified_since("not-a-date")
|
|
306
|
+
assert "Use ISO date/datetime" in str(excinfo.value)
|
|
307
|
+
|
|
308
|
+
# Cover cli.py line 368: except Exception as exc: (in render_consolidated)
|
|
309
|
+
def test_render_consolidated_skips_invalid_file(tmp_path):
|
|
310
|
+
from sec_report_kit.cli import app
|
|
311
|
+
from typer.testing import CliRunner
|
|
312
|
+
runner = CliRunner()
|
|
313
|
+
reports_dir = tmp_path / "security_reports"
|
|
314
|
+
reports_dir.mkdir()
|
|
315
|
+
# Write a file that will cause detect_source_type to raise ValueError
|
|
316
|
+
(reports_dir / "bad.json").write_text("{}")
|
|
317
|
+
output_dir = tmp_path / "out"
|
|
318
|
+
result = runner.invoke(
|
|
319
|
+
app,
|
|
320
|
+
[
|
|
321
|
+
"render",
|
|
322
|
+
"consolidated",
|
|
323
|
+
"--input",
|
|
324
|
+
str(reports_dir),
|
|
325
|
+
"--output",
|
|
326
|
+
str(output_dir),
|
|
327
|
+
],
|
|
328
|
+
)
|
|
329
|
+
assert result.exit_code == 0
|
|
330
|
+
assert "Skipping bad.json" in result.output
|
|
331
|
+
|
|
332
|
+
|
|
299
333
|
def test_main_module_runs():
|
|
300
334
|
"""Covers sec_report_kit/__main__.py by running the module as __main__."""
|
|
301
335
|
with patch("sec_report_kit.cli.main") as mock_main:
|
|
@@ -438,14 +472,20 @@ def test_render_consolidated_command_from_directory(tmp_path):
|
|
|
438
472
|
)
|
|
439
473
|
|
|
440
474
|
output_file = output_dir / "consolidated-security-report.html"
|
|
475
|
+
bandit_report = output_dir / "bandit-report.html"
|
|
476
|
+
trivy_report = output_dir / "trivy-report.html"
|
|
441
477
|
assert result.exit_code == 0
|
|
442
478
|
assert output_file.exists()
|
|
479
|
+
assert bandit_report.exists()
|
|
480
|
+
assert trivy_report.exists()
|
|
443
481
|
html = output_file.read_text()
|
|
444
482
|
assert "Security Audit Consolidated Dashboard" in html
|
|
445
483
|
assert "Reports available:" in html
|
|
446
484
|
assert "Target: repo-root" in html
|
|
447
485
|
assert "Bandit" in html
|
|
448
486
|
assert "Trivy" in html
|
|
487
|
+
assert 'src="bandit-report.html"' in html
|
|
488
|
+
assert 'src="trivy-report.html"' in html
|
|
449
489
|
|
|
450
490
|
|
|
451
491
|
def test_render_consolidated_command_empty_or_unsupported_directory(tmp_path):
|
|
@@ -503,6 +543,8 @@ def test_render_consolidated_command_modified_since_filters_candidates(tmp_path)
|
|
|
503
543
|
assert "Included trivy.json" in result.output
|
|
504
544
|
assert "Included bandit.json" not in result.output
|
|
505
545
|
assert "Files included: 1" in result.output
|
|
546
|
+
assert (output_dir / "trivy-report.html").exists()
|
|
547
|
+
assert not (output_dir / "bandit-report.html").exists()
|
|
506
548
|
|
|
507
549
|
|
|
508
550
|
def test_render_consolidated_command_modified_until_filters_candidates(tmp_path):
|
|
@@ -630,7 +672,6 @@ def test_generate_consolidated_wrapper_invokes_cli(monkeypatch, tmp_path):
|
|
|
630
672
|
input_dir = tmp_path / "reports"
|
|
631
673
|
output_dir = tmp_path / "out"
|
|
632
674
|
input_dir.mkdir()
|
|
633
|
-
|
|
634
675
|
monkeypatch.setattr(
|
|
635
676
|
"sys.argv",
|
|
636
677
|
[
|
|
@@ -666,6 +707,46 @@ def test_generate_consolidated_wrapper_invokes_cli(monkeypatch, tmp_path):
|
|
|
666
707
|
)
|
|
667
708
|
|
|
668
709
|
|
|
710
|
+
def test_detect_source_type_raises_value_error():
|
|
711
|
+
from sec_report_kit.parsers import detect_source_type
|
|
712
|
+
|
|
713
|
+
with pytest.raises(ValueError):
|
|
714
|
+
detect_source_type({"foo": "bar"})
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def test_detect_source_type_bandit_fallback_branch():
|
|
718
|
+
from sec_report_kit.parsers import detect_source_type
|
|
719
|
+
|
|
720
|
+
assert detect_source_type({"results": [{}]}) == "bandit"
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def test_write_consolidated_tool_reports_skips_unknown_parser(tmp_path):
|
|
724
|
+
from sec_report_kit.cli import _write_consolidated_tool_reports
|
|
725
|
+
|
|
726
|
+
findings_by_parser = {"unknown-parser": [{"dummy": 1}]}
|
|
727
|
+
_write_consolidated_tool_reports(tmp_path, "target", findings_by_parser)
|
|
728
|
+
|
|
729
|
+
assert not any(tmp_path.iterdir())
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def test_render_consolidated_defaults_output_to_input(tmp_path):
|
|
733
|
+
reports_dir = tmp_path / "security_reports"
|
|
734
|
+
reports_dir.mkdir()
|
|
735
|
+
(reports_dir / "trivy.json").write_text(json.dumps(TRIVY_PAYLOAD))
|
|
736
|
+
|
|
737
|
+
result = runner.invoke(
|
|
738
|
+
app,
|
|
739
|
+
[
|
|
740
|
+
"render",
|
|
741
|
+
"consolidated",
|
|
742
|
+
"--input",
|
|
743
|
+
str(reports_dir),
|
|
744
|
+
],
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
assert result.exit_code == 0
|
|
748
|
+
assert (reports_dir / "consolidated-security-report.html").exists()
|
|
749
|
+
|
|
669
750
|
def test_parse_modified_since_supports_named_ranges():
|
|
670
751
|
today = _parse_modified_since("today")
|
|
671
752
|
yesterday = _parse_modified_since("yesterday")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|