gitleaks-secret-scanner
Version:
A powerful, intelligent wrapper for the Gitleaks engine that provides accurate and safe secret scanning for local pre-commit hooks and CI/CD pipelines.
133 lines (126 loc) • 5.53 kB
JavaScript
const fs = require("fs");
const path = require("path");
const { version } = require("../package.json");
module.exports.generateHtmlReport = (leaksArray, htmlPath) => {
try {
const leaks = leaksArray || [];
function escapeHtml(unsafe) {
if (!unsafe) return "";
return unsafe
.toString()
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'");
}
const HTML_TEMPLATE = (reportData) => `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gitleaks Security Report</title>
<style>
:root { --primary: #2c3e50; --danger: #e74c3c; --success: #27ae60; --light: #ecf0f1; --dark: #34495e; --border-color: #e1e4e8; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f6f8fa; padding: 20px; }
.container { max-width: 1600px; margin: 0 auto; background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); overflow: hidden; }
header { background: var(--primary); color: white; padding: 25px 40px; text-align: center; border-bottom: 5px solid var(--danger); }
h1 { font-size: 2.2rem; margin: 0; }
.summary { display: flex; justify-content: space-around; padding: 20px; background: var(--light); border-bottom: 1px solid var(--border-color); }
.summary-item { text-align: center; }
h3 { font-size: 1.5rem; color: var(--primary); margin: 0; margin-bottom: 5px; }
.summary-item div { font-size: 1.8rem; font-weight: bold; color: var(--dark); }
.leak-count { font-size: 2.5rem; font-weight: bold; color: ${
reportData.leaks.length > 0 ? "var(--danger)" : "var(--success)"
}; }
.scan-info { padding: 10px 30px; background: #f1f1f1; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; font-size: 0.9em; color: #666; }
table { width: 100%; border-collapse: collapse; }
thead { background: var(--dark); color: white; }
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid var(--border-color); vertical-align: top; }
tbody tr:hover { background-color: #f1f8ff; }
.leak-description { max-width: 400px; white-space: normal; word-break: break-word; font-size: 0.95em; }
.rule-badge { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 0.85em; font-weight: 600; background-color: var(--light); color: var(--dark); border: 1px solid #ccc; }
.no-leaks { text-align: center; padding: 40px; color: var(--success); font-size: 1.2rem; }
footer { text-align: center; padding: 20px; color: #7f8c8d; font-size: 0.9rem; border-top: 1px solid var(--border-color); }
</style>
</head>
<body>
<div class="container">
<header><h1>Gitleaks Security Report</h1><p>Automated secrets detection report</p></header>
<div class="summary">
<div class="summary-item"><h3>Total Leaks</h3><div class="leak-count">${
reportData.leaks.length
}</div></div>
<div class="summary-item"><h3>Files Scanned</h3><div>${
reportData.filesScanned
}</div></div>
<div class="summary-item"><h3>Scan Time</h3><div>${
reportData.scanTime
}</div></div>
</div>
<div class="scan-info">
<div><strong>Scan Date:</strong> ${reportData.scanDate}</div>
<div><strong>Gitleaks Version:</strong> ${
reportData.gitleaksVersion
}</div>
</div>
${
reportData.leaks.length > 0
? `<table>
<thead>
<tr>
<th>File</th>
<th>Line</th>
<th>Secret Type</th>
<th>Description</th>
<th>Author</th>
<th>Date</th>
</tr>
</thead>
<tbody>
${reportData.leaks
.map(
(leak) => `<tr>
<td>${escapeHtml(leak.File)}</td>
<td>${leak.StartLine}</td>
<td><span class="rule-badge">${escapeHtml(
leak.RuleID
)}</span></td>
<td class="leak-description">${escapeHtml(
leak.Description
)}</td>
<td>${leak.Author ? escapeHtml(leak.Author) : "N/A"}</td>
<td>${
leak.Date ? new Date(leak.Date).toLocaleString() : "N/A"
}</td>
</tr>`
)
.join("")}
</tbody>
</table>`
: `<div class="no-leaks">✅ No secrets detected in scanned files</div>`
}
<footer>Generated by gitleaks-secret-scanner v${
reportData.packageVersion
} | ${reportData.leaks.length} potential secrets detected</footer>
</div>
</body>
</html>`;
const reportData = {
leaks: leaks,
filesScanned: new Set(leaks.map((l) => l.File)).size,
scanDate: new Date().toLocaleString(),
scanTime: new Date().toLocaleTimeString(),
gitleaksVersion: "8.27.2",
packageVersion: version,
};
const htmlContent = HTML_TEMPLATE(reportData);
const outputDir = path.dirname(htmlPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
fs.writeFileSync(htmlPath, htmlContent);
} catch (error) {
throw new Error(`HTML report generation failed: ${error.message}`);
}
};