File size: 6,770 Bytes
17e2892 96a9a65 a7f98d9 96a9a65 a7f98d9 96a9a65 a7f98d9 17e2892 a7f98d9 17e2892 ff5c846 17e2892 96a9a65 17e2892 9d01e0c 96a9a65 17e2892 ff5c846 17e2892 96a9a65 a7f98d9 9d01e0c 96a9a65 a7f98d9 96a9a65 ff5c846 17e2892 a7f98d9 17e2892 a7f98d9 17e2892 a7f98d9 17e2892 96a9a65 17e2892 a7f98d9 cef045e 9d01e0c 96a9a65 a7f98d9 9d01e0c 96a9a65 a7f98d9 96a9a65 a7f98d9 17e2892 96a9a65 cef045e 96a9a65 a7f98d9 96a9a65 17e2892 a7f98d9 17e2892 a7f98d9 17e2892 9d01e0c a7f98d9 9d01e0c a7f98d9 96a9a65 9d01e0c 96a9a65 a7f98d9 96a9a65 a7f98d9 9d01e0c 96a9a65 a7f98d9 96a9a65 a7f98d9 96a9a65 17e2892 a7f98d9 17e2892 a7f98d9 9d01e0c a7f98d9 9d01e0c a7f98d9 9d01e0c 17e2892 a7f98d9 e233eaf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | import subprocess
import json
import os
from typing import List, Dict
from pathlib import Path
BASE_DIR = Path(__file__).parent.parent.resolve()
TOOLS_DIR = BASE_DIR / "tools"
GITLEAKS_EXE = TOOLS_DIR / "gitleaks.exe"
TRIVY_EXE = TOOLS_DIR / "trivy.exe"
def normalize_path(path: str) -> str:
"Normalize file paths to use forward slashes for consistency across platforms."
path_str = str(path).replace("\\", "/").strip()
base_str = str(BASE_DIR).replace("\\", "/") + "/"
if path_str.startswith(base_str):
path_str = path_str[len(base_str):]
path_str = path_str.lstrip("./")
return path_str if path_str else ""
def run_gitleaks(repo_path: str= ".") -> list:
if not GITLEAKS_EXE.exists():
print("gitleaks.exe not found at", GITLEAKS_EXE)
return []
report_path= BASE_DIR/ "tmp" / "gitleaks-report.json"
os.makedirs(BASE_DIR / "tmp",exist_ok=True)
try:
cmd = [
str(GITLEAKS_EXE), "detect",
"--source", str(repo_path),
"--config", str(BASE_DIR / "gitleaks.toml"),
"--report-format", "json",
"--report-path", str(report_path),
"--no-git",
"--redact"
]
print("Running gitleaks with command:", " ".join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True, cwd= str(repo_path),timeout = 300)
print(f"Gitleaks exited with code : {result.returncode}")
if result.returncode not in [0,1]:
print("Gitleaks error output:", result.stderr.strip())
return []
if report_path.exists():
with open(report_path,"r",encoding="utf-8") as f:
findings = json.loads(f.read())
print(f"Gitleaks found {len(findings)} secrets.")
return findings if isinstance(findings, list ) else []
else:
print("Gitleaks found 0 secrets")
return []
except Exception as e:
print("Gitleaks execution error:", str(e))
return []
def run_trivy_fs(path: str = ".") -> list:
if not TRIVY_EXE.exists():
print("trivy.exe not found at", TRIVY_EXE)
return []
try:
cmd = [
str(TRIVY_EXE), "fs",
"--format", "json",
"--scanners", "vuln,secret,misconfig",
"--quiet",
"--skip-dirs",".venv,data,tmp,tools,node_modules",
str(path)
]
print("Running Trivy with command:", " ".join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True, cwd= str(path),timeout = 300)
print(f"Trivy exited with code : {result.returncode}")
if result.returncode != 0:
print("Trivy error output:", result.stderr.strip())
return []
data = json.loads(result.stdout) if result.stdout.strip() else {}
results = data.get("Results", [])
total_items = sum(
len(r.get("Vulnerabilities", [])) +
len(r.get("Secrets", [])) +
len(r.get("Misconfigurations", []))
for r in results
)
print(f"Trivy processed {len(results)} results with a total of {total_items} findings.")
return results
except Exception as e:
print("Trivy execution error:", str(e))
return []
def run_bandit(path: str = ".") -> list:
try:
scan_path=Path(path).resolve()
exclude_dirs = ",".join([
str(scan_path / ".venv"),
str(scan_path / "__pycache__"),
str(scan_path / "node_modules"),
str(scan_path / "tools"),
str(scan_path / "tmp"),
str(scan_path / "data"),
])
cmd = [
"bandit", "-r", str(scan_path),
"-f", "json",
"--quiet",
"--exclude", exclude_dirs
]
print("Running Bandit with command:", " ".join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True, cwd= str(scan_path),timeout = 300)
print(f"Bandit exited with code : {result.returncode}")
data= json.loads(result.stdout) if result.stdout.strip() else {}
findings = data.get("results", [])
print(f"Bandit found {len(findings)} issues.")
return findings
except Exception as e:
print("Bandit execution error:", str(e))
return []
def scan_all(target_dir: str=None) -> Dict[str, List[Dict]]:
"""Run all scanners. Returns dict with keys: gitleaks, trivy, bandit.
Empty list per key means either clean scan OR scanner failure — check logs to distinguish."""
scan_target=Path(target_dir).resolve() if target_dir else BASE_DIR
print(f"Starting comprehensive scan from : {scan_target}")
findings={
"gitleaks": run_gitleaks(str(scan_target)),
"trivy": run_trivy_fs(str(scan_target)),
"bandit": run_bandit(str(scan_target))
}
total_findings ={k : len(v) for k,v in findings.items()}
print(f"Scan completed with findings: {total_findings}")
return findings
def generate_sbom(path: str = None, format: str = "cyclonedx") -> dict:
"""
Generate Software Bill of Materials using Trivy.
Args:
path: directory to scan (defaults to BASE_DIR)
format: 'cyclonedx' or 'spdx-json'
Returns:
dict with sbom data and metadata
"""
if not TRIVY_EXE.exists():
return {"error": "trivy.exe not found"}
scan_path = Path(path).resolve() if path else BASE_DIR
output_file = BASE_DIR / "tmp" / f"sbom-{format}.json"
os.makedirs(BASE_DIR / "tmp", exist_ok=True)
cmd = [
str(TRIVY_EXE), "fs",
"--format", format,
"--output", str(output_file),
"--quiet",
str(scan_path)
]
try:
result = subprocess.run(
cmd, capture_output=True, text=True,
cwd=str(scan_path), timeout=120
)
if result.returncode != 0:
return {"error": result.stderr.strip()}
if output_file.exists():
with open(output_file, "r", encoding="utf-8") as f:
sbom_data = json.load(f)
return {
"format": format,
"path": str(scan_path),
"component_count": _count_components(sbom_data, format),
"sbom": sbom_data
}
return {"error": "SBOM file not generated"}
except Exception as e:
return {"error": str(e)}
def _count_components(sbom_data: dict, format: str) -> int:
"""Count components in SBOM output."""
if format == "cyclonedx":
return len(sbom_data.get("components", []))
elif format == "spdx-json":
return len(sbom_data.get("packages", []))
return 0 |