mirror of
https://github.com/momenbasel/keyFinder.git
synced 2026-04-23 21:16:00 +02:00
b73c2185b0
- 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
250 lines
8.3 KiB
JavaScript
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();
|
|
}
|