mirror of
https://github.com/momenbasel/keyFinder.git
synced 2026-07-25 14:10:49 +02:00
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
This commit is contained in:
+233
-71
@@ -1,87 +1,249 @@
|
||||
//Searching localStorage for URL
|
||||
var i;
|
||||
for(i=0; i < localStorage.length; i++) {
|
||||
//regex to filter keywords from localStorage and showing URLS
|
||||
if(/(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(localStorage.key(i))){
|
||||
$('.table').append(`
|
||||
<tr>
|
||||
<td scope="row"></td>
|
||||
<td>${extractHostname(localStorage.key(i))}</td>
|
||||
<td> <a href='${localStorage.key(i)}'>${localStorage.key(i)}</a> <button value=${localStorage.key(i)} class="btn"><i class="fa fa-trash"></i></button> </td>
|
||||
<td>${searchKeywordOnURL(localStorage.key(i))}</td>
|
||||
<tr>
|
||||
<style>
|
||||
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;
|
||||
|
||||
|
||||
function searchKeywordOnURL(key) {
|
||||
let i;
|
||||
var keyword;
|
||||
for(i=0; i < localStorage.length; i++) {
|
||||
//regex to filter keywords from localStorage and showing URLS
|
||||
if(!/(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(localStorage.key(i))){
|
||||
if(key.indexOf(localStorage.key(i)) !== -1) {
|
||||
return localStorage.key(i);
|
||||
}
|
||||
}
|
||||
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 = "";
|
||||
|
||||
//numbering results && self-inovking the function
|
||||
(function(cl){
|
||||
var table = document.querySelector('table.' + cl)
|
||||
var trs = table.querySelectorAll('tr')
|
||||
var counter = 1
|
||||
if (filtered.length === 0) {
|
||||
empty.hidden = false;
|
||||
return;
|
||||
}
|
||||
empty.hidden = true;
|
||||
|
||||
Array.prototype.forEach.call(trs, function(x,i){
|
||||
var firstChild = x.children[0]
|
||||
if (firstChild.tagName === 'TD') {
|
||||
var cell = document.createElement('td')
|
||||
cell.textContent = counter ++
|
||||
x.insertBefore(cell,firstChild)
|
||||
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 {
|
||||
firstChild.setAttribute('colspan',2)
|
||||
}
|
||||
})
|
||||
})("table");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//delete
|
||||
$('.btn').click(function() {
|
||||
var selectedItem = $(this).val();
|
||||
localStorage.removeItem(selectedItem);
|
||||
location.reload();
|
||||
alert(`${selectedItem} removed`);
|
||||
})
|
||||
|
||||
//extract domain name from URL
|
||||
function extractHostname(url) {
|
||||
var hostname;
|
||||
//find & remove protocol (http, ftp, etc.) and get hostname
|
||||
|
||||
if (url.indexOf("//") > -1) {
|
||||
hostname = url.split('/')[2];
|
||||
}
|
||||
else {
|
||||
hostname = url.split('/')[0];
|
||||
tdSource.textContent = f.url ? truncateUrl(f.url, 40) : "-";
|
||||
}
|
||||
|
||||
//find & remove port number
|
||||
hostname = hostname.split(':')[0];
|
||||
//find & remove "?"
|
||||
hostname = hostname.split('?')[0];
|
||||
const tdTime = document.createElement("td");
|
||||
tdTime.textContent = f.timestamp ? formatTime(f.timestamp) : "-";
|
||||
tdTime.className = "td-time";
|
||||
|
||||
return hostname;
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user