Files
moamen b73c2185b0 v2.0.0: Complete rewrite - Manifest V3, enterprise-grade secret detection
- Migrated to Chrome Manifest V3 with service worker architecture
- 80+ secret detection patterns covering AWS, GCP, Azure, GitHub, GitLab,
  Stripe, Slack, Discord, OpenAI, and 30+ other providers
- 10 scanning surfaces: inline scripts, external scripts, meta tags,
  hidden inputs, data attributes, HTML comments, URL params, web storage,
  cookies, and network response interception
- Shannon entropy analysis for detecting undocumented secret formats
- MAIN world interceptor for XHR/fetch response scanning and window globals
- Professional dark-theme UI with filtering, search, and CSV/JSON export
- Zero dependencies - removed jQuery, Bootstrap, font-awesome, popper
- Proper XSS-safe DOM rendering throughout
- Badge counter on extension icon showing finding count
- All frames scanning including iframes
2026-04-07 18:22:42 +02:00

250 lines
8.3 KiB
JavaScript

let allFindings = [];
document.addEventListener("DOMContentLoaded", init);
async function init() {
const response = await chrome.runtime.sendMessage({ type: "getFindings" });
allFindings = response.findings || [];
populateFilters();
renderStats();
renderFindings();
document.getElementById("severityFilter").addEventListener("change", renderFindings);
document.getElementById("typeFilter").addEventListener("change", renderFindings);
document.getElementById("providerFilter").addEventListener("change", renderFindings);
document.getElementById("searchBox").addEventListener("input", renderFindings);
document.getElementById("exportJsonBtn").addEventListener("click", exportJson);
document.getElementById("exportCsvBtn").addEventListener("click", exportCsv);
document.getElementById("clearBtn").addEventListener("click", clearAll);
}
function getFiltered() {
const severity = document.getElementById("severityFilter").value;
const type = document.getElementById("typeFilter").value;
const provider = document.getElementById("providerFilter").value;
const search = document.getElementById("searchBox").value.toLowerCase();
return allFindings.filter((f) => {
if (severity !== "all" && f.severity !== severity) return false;
if (type !== "all" && f.type !== type) return false;
if (provider !== "all" && f.provider !== provider) return false;
if (search && !JSON.stringify(f).toLowerCase().includes(search)) return false;
return true;
});
}
function populateFilters() {
const types = [...new Set(allFindings.map((f) => f.type))].sort();
const providers = [...new Set(allFindings.map((f) => f.provider))].sort();
const typeSelect = document.getElementById("typeFilter");
for (const t of types) {
const opt = document.createElement("option");
opt.value = t;
opt.textContent = t;
typeSelect.appendChild(opt);
}
const providerSelect = document.getElementById("providerFilter");
for (const p of providers) {
const opt = document.createElement("option");
opt.value = p;
opt.textContent = p;
providerSelect.appendChild(opt);
}
}
function renderStats() {
const bar = document.getElementById("statsBar");
const critical = allFindings.filter((f) => f.severity === "critical").length;
const high = allFindings.filter((f) => f.severity === "high").length;
const medium = allFindings.filter((f) => f.severity === "medium").length;
const low = allFindings.filter((f) => f.severity === "low").length;
const domains = new Set(allFindings.map((f) => f.domain)).size;
bar.innerHTML = "";
const stats = [
{ label: "Total", value: allFindings.length, cls: "stat-total" },
{ label: "Critical", value: critical, cls: "stat-critical" },
{ label: "High", value: high, cls: "stat-high" },
{ label: "Medium", value: medium, cls: "stat-medium" },
{ label: "Low", value: low, cls: "stat-low" },
{ label: "Domains", value: domains, cls: "stat-domains" },
];
for (const s of stats) {
const el = document.createElement("div");
el.className = `stat-item ${s.cls}`;
const num = document.createElement("span");
num.className = "stat-num";
num.textContent = s.value;
const lbl = document.createElement("span");
lbl.className = "stat-lbl";
lbl.textContent = s.label;
el.appendChild(num);
el.appendChild(lbl);
bar.appendChild(el);
}
}
function renderFindings() {
const filtered = getFiltered();
const tbody = document.getElementById("findingsBody");
const empty = document.getElementById("emptyState");
tbody.innerHTML = "";
if (filtered.length === 0) {
empty.hidden = false;
return;
}
empty.hidden = true;
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
filtered.sort((a, b) => (severityOrder[a.severity] || 5) - (severityOrder[b.severity] || 5));
filtered.forEach((f, i) => {
const tr = document.createElement("tr");
const tdNum = document.createElement("td");
tdNum.textContent = i + 1;
const tdSev = document.createElement("td");
const badge = document.createElement("span");
badge.className = `badge badge-${f.severity || "medium"}`;
badge.textContent = (f.severity || "medium").toUpperCase();
tdSev.appendChild(badge);
const tdProvider = document.createElement("td");
tdProvider.textContent = f.provider || "-";
tdProvider.className = "td-provider";
const tdPattern = document.createElement("td");
tdPattern.textContent = f.patternName || "-";
tdPattern.className = "td-pattern";
const tdMatch = document.createElement("td");
const matchCode = document.createElement("code");
matchCode.textContent = f.match || "-";
matchCode.className = "match-value";
matchCode.title = f.match || "";
tdMatch.appendChild(matchCode);
const tdType = document.createElement("td");
const typeBadge = document.createElement("span");
typeBadge.className = "type-badge";
typeBadge.textContent = f.type || "-";
tdType.appendChild(typeBadge);
const tdDomain = document.createElement("td");
tdDomain.textContent = f.domain || "-";
tdDomain.className = "td-domain";
const tdSource = document.createElement("td");
if (f.url && f.url.startsWith("http")) {
const a = document.createElement("a");
a.href = f.url;
a.target = "_blank";
a.rel = "noopener";
a.textContent = truncateUrl(f.url, 40);
a.title = f.url;
tdSource.appendChild(a);
} else {
tdSource.textContent = f.url ? truncateUrl(f.url, 40) : "-";
}
const tdTime = document.createElement("td");
tdTime.textContent = f.timestamp ? formatTime(f.timestamp) : "-";
tdTime.className = "td-time";
const tdActions = document.createElement("td");
const copyBtn = document.createElement("button");
copyBtn.className = "btn-icon";
copyBtn.textContent = "Copy";
copyBtn.title = "Copy match value";
copyBtn.addEventListener("click", () => {
navigator.clipboard.writeText(f.match || "");
copyBtn.textContent = "Done";
setTimeout(() => (copyBtn.textContent = "Copy"), 1500);
});
const delBtn = document.createElement("button");
delBtn.className = "btn-icon btn-icon-danger";
delBtn.textContent = "Del";
delBtn.title = "Remove finding";
delBtn.addEventListener("click", async () => {
await chrome.runtime.sendMessage({ type: "removeFinding", url: f.url });
allFindings = allFindings.filter((x) => x !== f);
renderStats();
renderFindings();
});
tdActions.appendChild(copyBtn);
tdActions.appendChild(delBtn);
tr.appendChild(tdNum);
tr.appendChild(tdSev);
tr.appendChild(tdProvider);
tr.appendChild(tdPattern);
tr.appendChild(tdMatch);
tr.appendChild(tdType);
tr.appendChild(tdDomain);
tr.appendChild(tdSource);
tr.appendChild(tdTime);
tr.appendChild(tdActions);
tbody.appendChild(tr);
});
}
function truncateUrl(url, max) {
if (url.length <= max) return url;
return url.substring(0, max - 3) + "...";
}
function formatTime(ts) {
const d = new Date(ts);
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
function exportJson() {
const filtered = getFiltered();
const blob = new Blob([JSON.stringify(filtered, null, 2)], { type: "application/json" });
downloadBlob(blob, `keyfinder-findings-${Date.now()}.json`);
}
function exportCsv() {
const filtered = getFiltered();
const headers = ["Severity", "Provider", "Pattern", "Match", "Type", "Domain", "URL", "Page URL", "Timestamp"];
const rows = filtered.map((f) => [
f.severity || "",
f.provider || "",
f.patternName || "",
`"${(f.match || "").replace(/"/g, '""')}"`,
f.type || "",
f.domain || "",
f.url || "",
f.pageUrl || "",
f.timestamp ? new Date(f.timestamp).toISOString() : "",
]);
const csv = [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
const blob = new Blob([csv], { type: "text/csv" });
downloadBlob(blob, `keyfinder-findings-${Date.now()}.csv`);
}
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
async function clearAll() {
if (!confirm("Remove all findings?")) return;
await chrome.runtime.sendMessage({ type: "clearFindings" });
allFindings = [];
renderStats();
renderFindings();
}