sec-report-kit 0.2.4__tar.gz → 0.2.5__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.4 → sec_report_kit-0.2.5}/CHANGELOG.md +14 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/PKG-INFO +21 -1
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/README.md +20 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/pyproject.toml +1 -1
- sec_report_kit-0.2.5/scripts/render_consolidated_html.bat +25 -0
- sec_report_kit-0.2.5/scripts/render_consolidated_html.sh +34 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/__init__.py +1 -1
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/cli.py +96 -24
- sec_report_kit-0.2.5/src/sec_report_kit/generate_consolidated_security_report.py +27 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/tests/test_cli.py +93 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.gitattributes +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.github/workflows/pip-audit.yml +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.github/workflows/publish-pypi.yml +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.github/workflows/publish-testpypi.yml +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.github/workflows/trivy-fs-audit.yml +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.github/workflows/unit-test-coverage-gate.yml +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/.gitignore +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/bandit-gaps-vs-sast-open-source-tools.md +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/check_assets.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/requirements.txt +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/download_safety_db.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/download_safety_db.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/download_safety_db.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/install_external_clis.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/install_tools.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/install_tools.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_bandit_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_bandit_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_checkov_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_checkov_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_codeql_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_codeql_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_gitleaks_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_gitleaks_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_osv_scanner_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_osv_scanner_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_pip_audit_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_pip_audit_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_safety_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_safety_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_semgrep_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_semgrep_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_tfsec_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_tfsec_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_trivy_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_trivy_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_trufflehog_html.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/render_trufflehog_html.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_bandit.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_bandit.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_checkov.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_checkov.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_codeql.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_codeql.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_gitleaks.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_gitleaks.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_osv_scanner.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_osv_scanner.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_pip_audit.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_pip_audit.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_safety.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_safety.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_semgrep.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_semgrep.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_tfsec.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_tfsec.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_trivy.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_trivy.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_trufflehog.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_trufflehog.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_unit_tests_with_coverage.bat +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/scripts/run_unit_tests_with_coverage.sh +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/__main__.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/mcp/__init__.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/mcp/server.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/models.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/__init__.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/bandit.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/checkov.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/codeql.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/gitleaks.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/osv_scanner.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/pip_audit.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/safety.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/semgrep.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/tfsec.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/trivy.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/parsers/trufflehog.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/report/__init__.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/report/html_renderer.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/services/__init__.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/services/normalize.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/src/sec_report_kit/services/summarize.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/tests/test_html_renderer.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/tests/test_mcp_server.py +0 -0
- {sec_report_kit-0.2.4 → sec_report_kit-0.2.5}/tests/test_parsers.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.2.5 - 2026-05-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- New CLI command: `srk render consolidated` to build a single HTML report from all supported scanner reports in an input folder.
|
|
9
|
+
- New helper scripts for consolidated rendering:
|
|
10
|
+
- `scripts/render_consolidated_html.sh`
|
|
11
|
+
- `scripts/render_consolidated_html.bat`
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `src/sec_report_kit/generate_consolidated_security_report.py` now acts as a wrapper that accepts `--input`, `--output`, and `--target`, and delegates to the consolidated CLI renderer.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Unified package version metadata by aligning `src/sec_report_kit/__init__.py` with the project version.
|
|
18
|
+
|
|
5
19
|
## 0.2.4 - 2026-05-14
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sec-report-kit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
@@ -82,6 +82,14 @@ Render Safety JSON:
|
|
|
82
82
|
srk render safety --input security_reports/safety.json --output security_reports/report-safety.html --target requirements.txt
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Render a consolidated HTML report from all supported scanner files in a directory:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
srk render consolidated --input security_reports --output security_reports --target sec-report-kit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This command writes `consolidated-security-report.html` to the output folder.
|
|
92
|
+
|
|
85
93
|
## Helper Scripts (bat/sh)
|
|
86
94
|
|
|
87
95
|
Cross-platform helper scripts are available in `scripts/`.
|
|
@@ -232,6 +240,18 @@ scripts\render_trivy_html.bat
|
|
|
232
240
|
scripts\render_trivy_html.bat security_reports my-image
|
|
233
241
|
```
|
|
234
242
|
|
|
243
|
+
Convert all supported scanner reports in a folder to one consolidated HTML report:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Linux/macOS (optional args: <report_dir> <target_name>)
|
|
247
|
+
bash scripts/render_consolidated_html.sh
|
|
248
|
+
bash scripts/render_consolidated_html.sh security_reports sec-report-kit
|
|
249
|
+
|
|
250
|
+
# Windows (optional args: <report_dir> <target_name>)
|
|
251
|
+
scripts\render_consolidated_html.bat
|
|
252
|
+
scripts\render_consolidated_html.bat security_reports sec-report-kit
|
|
253
|
+
```
|
|
254
|
+
|
|
235
255
|
Convert sample Bandit JSON report to HTML:
|
|
236
256
|
|
|
237
257
|
```bash
|
|
@@ -55,6 +55,14 @@ Render Safety JSON:
|
|
|
55
55
|
srk render safety --input security_reports/safety.json --output security_reports/report-safety.html --target requirements.txt
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
Render a consolidated HTML report from all supported scanner files in a directory:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
srk render consolidated --input security_reports --output security_reports --target sec-report-kit
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This command writes `consolidated-security-report.html` to the output folder.
|
|
65
|
+
|
|
58
66
|
## Helper Scripts (bat/sh)
|
|
59
67
|
|
|
60
68
|
Cross-platform helper scripts are available in `scripts/`.
|
|
@@ -205,6 +213,18 @@ scripts\render_trivy_html.bat
|
|
|
205
213
|
scripts\render_trivy_html.bat security_reports my-image
|
|
206
214
|
```
|
|
207
215
|
|
|
216
|
+
Convert all supported scanner reports in a folder to one consolidated HTML report:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Linux/macOS (optional args: <report_dir> <target_name>)
|
|
220
|
+
bash scripts/render_consolidated_html.sh
|
|
221
|
+
bash scripts/render_consolidated_html.sh security_reports sec-report-kit
|
|
222
|
+
|
|
223
|
+
# Windows (optional args: <report_dir> <target_name>)
|
|
224
|
+
scripts\render_consolidated_html.bat
|
|
225
|
+
scripts\render_consolidated_html.bat security_reports sec-report-kit
|
|
226
|
+
```
|
|
227
|
+
|
|
208
228
|
Convert sample Bandit JSON report to HTML:
|
|
209
229
|
|
|
210
230
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sec-report-kit"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
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"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal
|
|
3
|
+
|
|
4
|
+
set "PYTHON_BIN=python"
|
|
5
|
+
if exist "%~dp0..\.venv\Scripts\python.exe" set "PYTHON_BIN=%~dp0..\.venv\Scripts\python.exe"
|
|
6
|
+
|
|
7
|
+
set "REPORT_DIR=%~1"
|
|
8
|
+
if "%REPORT_DIR%"=="" set "REPORT_DIR=security_reports"
|
|
9
|
+
|
|
10
|
+
set "TARGET_NAME=%~2"
|
|
11
|
+
if "%TARGET_NAME%"=="" for %%I in ("%~dp0..") do set "TARGET_NAME=%%~nxI"
|
|
12
|
+
|
|
13
|
+
if not exist "%REPORT_DIR%" (
|
|
14
|
+
echo Missing input directory: %REPORT_DIR%
|
|
15
|
+
exit /b 1
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
set "OUT_HTML=%REPORT_DIR%\consolidated-security-report.html"
|
|
19
|
+
|
|
20
|
+
"%PYTHON_BIN%" -m sec_report_kit render consolidated --input "%REPORT_DIR%" --output "%REPORT_DIR%" --target "%TARGET_NAME%"
|
|
21
|
+
if errorlevel 1 exit /b 1
|
|
22
|
+
|
|
23
|
+
echo HTML report written to %OUT_HTML%
|
|
24
|
+
for %%I in ("%OUT_HTML%") do set "OUT_HTML_ABS=%%~fI"
|
|
25
|
+
start "" "%OUT_HTML_ABS%"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PYTHON_BIN="${PYTHON_BIN:-python}"
|
|
5
|
+
if [[ -x "$(dirname "$0")/../.venv/bin/python" ]]; then
|
|
6
|
+
PYTHON_BIN="$(dirname "$0")/../.venv/bin/python"
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
REPORT_DIR="${1:-security_reports}"
|
|
10
|
+
ROOT_NAME="$(basename "$(cd "$(dirname "$0")/.." && pwd)")"
|
|
11
|
+
TARGET_NAME="${2:-${ROOT_NAME}}"
|
|
12
|
+
OUT_HTML="${REPORT_DIR}/consolidated-security-report.html"
|
|
13
|
+
|
|
14
|
+
open_html() {
|
|
15
|
+
if command -v xdg-open >/dev/null 2>&1; then
|
|
16
|
+
xdg-open "$1" >/dev/null 2>&1 &
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
if command -v open >/dev/null 2>&1; then
|
|
20
|
+
open "$1" >/dev/null 2>&1 &
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
23
|
+
echo "HTML generated but no supported browser opener was found. Open manually: $1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if [[ ! -d "${REPORT_DIR}" ]]; then
|
|
27
|
+
echo "Missing input directory: ${REPORT_DIR}"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
"${PYTHON_BIN}" -m sec_report_kit render consolidated --input "${REPORT_DIR}" --output "${REPORT_DIR}" --target "${TARGET_NAME}"
|
|
32
|
+
|
|
33
|
+
echo "HTML report written to ${OUT_HTML}"
|
|
34
|
+
open_html "${OUT_HTML}"
|
|
@@ -44,35 +44,38 @@ def _load_json(path: Path) -> Any:
|
|
|
44
44
|
return items
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def _parse_findings(payload: Any, parser: str):
|
|
48
|
+
if parser == "trivy":
|
|
49
|
+
return parse_trivy_json(payload)
|
|
50
|
+
if parser == "pip-audit":
|
|
51
|
+
return parse_pip_audit_json(payload)
|
|
52
|
+
if parser == "safety":
|
|
53
|
+
return parse_safety_json(payload)
|
|
54
|
+
if parser == "bandit":
|
|
55
|
+
return parse_bandit_json(payload)
|
|
56
|
+
if parser == "gitleaks":
|
|
57
|
+
return parse_gitleaks_json(payload)
|
|
58
|
+
if parser == "semgrep":
|
|
59
|
+
return parse_semgrep_json(payload)
|
|
60
|
+
if parser == "codeql":
|
|
61
|
+
return parse_codeql_json(payload)
|
|
62
|
+
if parser == "osv-scanner":
|
|
63
|
+
return parse_osv_scanner_json(payload)
|
|
64
|
+
if parser == "checkov":
|
|
65
|
+
return parse_checkov_json(payload)
|
|
66
|
+
if parser == "tfsec":
|
|
67
|
+
return parse_tfsec_json(payload)
|
|
68
|
+
if parser == "trufflehog":
|
|
69
|
+
return parse_trufflehog_json(payload)
|
|
70
|
+
raise typer.BadParameter(f"Unsupported parser: {parser}")
|
|
71
|
+
|
|
72
|
+
|
|
47
73
|
def _write_report(source_label: str, target_ref: str, input_path: Path, output_path: Path, parser: str) -> None:
|
|
48
74
|
payload = _load_json(input_path)
|
|
49
75
|
if parser == "auto":
|
|
50
76
|
parser = detect_source_type(payload)
|
|
51
77
|
typer.echo(f"[INFO] Detected source type: {parser}")
|
|
52
|
-
|
|
53
|
-
findings = parse_trivy_json(payload)
|
|
54
|
-
elif parser == "pip-audit":
|
|
55
|
-
findings = parse_pip_audit_json(payload)
|
|
56
|
-
elif parser == "safety":
|
|
57
|
-
findings = parse_safety_json(payload)
|
|
58
|
-
elif parser == "bandit":
|
|
59
|
-
findings = parse_bandit_json(payload)
|
|
60
|
-
elif parser == "gitleaks":
|
|
61
|
-
findings = parse_gitleaks_json(payload)
|
|
62
|
-
elif parser == "semgrep":
|
|
63
|
-
findings = parse_semgrep_json(payload)
|
|
64
|
-
elif parser == "codeql":
|
|
65
|
-
findings = parse_codeql_json(payload)
|
|
66
|
-
elif parser == "osv-scanner":
|
|
67
|
-
findings = parse_osv_scanner_json(payload)
|
|
68
|
-
elif parser == "checkov":
|
|
69
|
-
findings = parse_checkov_json(payload)
|
|
70
|
-
elif parser == "tfsec":
|
|
71
|
-
findings = parse_tfsec_json(payload)
|
|
72
|
-
elif parser == "trufflehog":
|
|
73
|
-
findings = parse_trufflehog_json(payload)
|
|
74
|
-
else:
|
|
75
|
-
raise typer.BadParameter(f"Unsupported parser: {parser}")
|
|
78
|
+
findings = _parse_findings(payload, parser)
|
|
76
79
|
|
|
77
80
|
findings = sort_findings(findings)
|
|
78
81
|
counts = count_by_severity(findings)
|
|
@@ -205,6 +208,75 @@ def render_trufflehog(
|
|
|
205
208
|
_write_report("trufflehog", target, input, output, parser="trufflehog")
|
|
206
209
|
|
|
207
210
|
|
|
211
|
+
@render_app.command("consolidated")
|
|
212
|
+
def render_consolidated(
|
|
213
|
+
input: Path = typer.Option(
|
|
214
|
+
...,
|
|
215
|
+
"--input",
|
|
216
|
+
exists=True,
|
|
217
|
+
dir_okay=True,
|
|
218
|
+
file_okay=False,
|
|
219
|
+
readable=True,
|
|
220
|
+
help="Folder containing scanner JSON/SARIF report files",
|
|
221
|
+
),
|
|
222
|
+
output: Path = typer.Option(
|
|
223
|
+
...,
|
|
224
|
+
"--output",
|
|
225
|
+
dir_okay=True,
|
|
226
|
+
file_okay=False,
|
|
227
|
+
help="Output folder where consolidated HTML will be written",
|
|
228
|
+
),
|
|
229
|
+
target: str = typer.Option("consolidated-scan", "--target", help="Consolidated target label"),
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Render a consolidated HTML report from all supported report files in a directory."""
|
|
232
|
+
candidates = sorted(
|
|
233
|
+
[
|
|
234
|
+
path
|
|
235
|
+
for path in input.iterdir()
|
|
236
|
+
if path.is_file()
|
|
237
|
+
and (
|
|
238
|
+
path.suffix.lower() in {".json", ".sarif", ".ndjson", ".jsonl"}
|
|
239
|
+
or path.name.lower().endswith(".sarif.json")
|
|
240
|
+
)
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
all_findings = []
|
|
245
|
+
included_files = 0
|
|
246
|
+
skipped_files = 0
|
|
247
|
+
for report_file in candidates:
|
|
248
|
+
try:
|
|
249
|
+
payload = _load_json(report_file)
|
|
250
|
+
parser = detect_source_type(payload)
|
|
251
|
+
findings = _parse_findings(payload, parser)
|
|
252
|
+
all_findings.extend(findings)
|
|
253
|
+
included_files += 1
|
|
254
|
+
typer.echo(f"[INFO] Included {report_file.name} as {parser} ({len(findings)} findings)")
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
skipped_files += 1
|
|
257
|
+
typer.echo(f"[WARN] Skipping {report_file.name}: {exc}")
|
|
258
|
+
|
|
259
|
+
all_findings = sort_findings(all_findings)
|
|
260
|
+
counts = count_by_severity(all_findings)
|
|
261
|
+
report_html = render_html_report(
|
|
262
|
+
target_ref=target,
|
|
263
|
+
source_label="consolidated",
|
|
264
|
+
findings=all_findings,
|
|
265
|
+
counts=counts,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
output_file = output / "consolidated-security-report.html"
|
|
270
|
+
output_file.write_text(report_html, encoding="utf-8")
|
|
271
|
+
|
|
272
|
+
typer.echo(f"[OK] Report generated: {output_file}")
|
|
273
|
+
typer.echo(f"[INFO] Input directory: {input}")
|
|
274
|
+
typer.echo(f"[INFO] Files included: {included_files}")
|
|
275
|
+
typer.echo(f"[INFO] Files skipped: {skipped_files}")
|
|
276
|
+
typer.echo(f"[INFO] Total findings: {len(all_findings)}")
|
|
277
|
+
typer.echo(f"[INFO] Severity counts: {counts}")
|
|
278
|
+
|
|
279
|
+
|
|
208
280
|
@mcp_app.command("serve")
|
|
209
281
|
def serve_mcp(
|
|
210
282
|
transport: str = typer.Option("stdio", "--transport", help="MCP transport (currently stdio)")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from sec_report_kit.cli import render_consolidated
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> int:
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="Generate a consolidated HTML security report from scanner JSON/SARIF files"
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument("--input", required=True, help="Input folder containing report files")
|
|
14
|
+
parser.add_argument("--output", required=True, help="Output folder for generated HTML")
|
|
15
|
+
parser.add_argument("--target", default="consolidated-scan", help="Target label used in HTML report")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
render_consolidated(
|
|
19
|
+
input=Path(args.input),
|
|
20
|
+
output=Path(args.output),
|
|
21
|
+
target=args.target,
|
|
22
|
+
)
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
raise SystemExit(main())
|
|
@@ -403,3 +403,96 @@ def test_render_tfsec_command(tmp_path):
|
|
|
403
403
|
assert result.exit_code == 0
|
|
404
404
|
assert output_file.exists()
|
|
405
405
|
assert "AWS001" in output_file.read_text()
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_render_consolidated_command_from_directory(tmp_path):
|
|
409
|
+
reports_dir = tmp_path / "security_reports"
|
|
410
|
+
reports_dir.mkdir()
|
|
411
|
+
|
|
412
|
+
trivy_file = reports_dir / "trivy.json"
|
|
413
|
+
trivy_file.write_text(json.dumps(TRIVY_PAYLOAD))
|
|
414
|
+
|
|
415
|
+
bandit_file = reports_dir / "bandit.json"
|
|
416
|
+
bandit_file.write_text(json.dumps(BANDIT_PAYLOAD))
|
|
417
|
+
|
|
418
|
+
# Non-report content should be skipped safely.
|
|
419
|
+
ignored_file = reports_dir / "notes.json"
|
|
420
|
+
ignored_file.write_text(json.dumps({"hello": "world"}))
|
|
421
|
+
|
|
422
|
+
output_dir = tmp_path / "out"
|
|
423
|
+
|
|
424
|
+
result = runner.invoke(
|
|
425
|
+
app,
|
|
426
|
+
[
|
|
427
|
+
"render",
|
|
428
|
+
"consolidated",
|
|
429
|
+
"--input",
|
|
430
|
+
str(reports_dir),
|
|
431
|
+
"--output",
|
|
432
|
+
str(output_dir),
|
|
433
|
+
"--target",
|
|
434
|
+
"repo-root",
|
|
435
|
+
],
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
output_file = output_dir / "consolidated-security-report.html"
|
|
439
|
+
assert result.exit_code == 0
|
|
440
|
+
assert output_file.exists()
|
|
441
|
+
html = output_file.read_text()
|
|
442
|
+
assert "CVE-2024-0001" in html
|
|
443
|
+
assert "B101" in html
|
|
444
|
+
assert "Source: <strong>consolidated</strong>" in html
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def test_render_consolidated_command_empty_or_unsupported_directory(tmp_path):
|
|
448
|
+
reports_dir = tmp_path / "security_reports"
|
|
449
|
+
reports_dir.mkdir()
|
|
450
|
+
(reports_dir / "unsupported.json").write_text(json.dumps({"invalid": True}))
|
|
451
|
+
|
|
452
|
+
output_dir = tmp_path / "out"
|
|
453
|
+
result = runner.invoke(
|
|
454
|
+
app,
|
|
455
|
+
["render", "consolidated", "--input", str(reports_dir), "--output", str(output_dir)],
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
output_file = output_dir / "consolidated-security-report.html"
|
|
459
|
+
assert result.exit_code == 0
|
|
460
|
+
assert output_file.exists()
|
|
461
|
+
html = output_file.read_text()
|
|
462
|
+
assert "No vulnerabilities found." in html
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_generate_consolidated_wrapper_invokes_cli(monkeypatch, tmp_path):
|
|
466
|
+
input_dir = tmp_path / "reports"
|
|
467
|
+
output_dir = tmp_path / "out"
|
|
468
|
+
input_dir.mkdir()
|
|
469
|
+
|
|
470
|
+
monkeypatch.setattr(
|
|
471
|
+
"sys.argv",
|
|
472
|
+
[
|
|
473
|
+
"generate_consolidated_security_report.py",
|
|
474
|
+
"--input",
|
|
475
|
+
str(input_dir),
|
|
476
|
+
"--output",
|
|
477
|
+
str(output_dir),
|
|
478
|
+
"--target",
|
|
479
|
+
"repo-root",
|
|
480
|
+
],
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
with patch("sec_report_kit.generate_consolidated_security_report.render_consolidated") as mock_render:
|
|
484
|
+
from sec_report_kit.generate_consolidated_security_report import main as wrapper_main
|
|
485
|
+
|
|
486
|
+
exit_code = wrapper_main()
|
|
487
|
+
|
|
488
|
+
assert exit_code == 0
|
|
489
|
+
mock_render.assert_called_once_with(
|
|
490
|
+
input=input_dir,
|
|
491
|
+
output=output_dir,
|
|
492
|
+
target="repo-root",
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def test_consolidated_helper_scripts_exist():
|
|
497
|
+
assert Path("scripts/render_consolidated_html.bat").exists()
|
|
498
|
+
assert Path("scripts/render_consolidated_html.sh").exists()
|
|
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
|