mirror of
https://github.com/momenbasel/keyFinder.git
synced 2026-06-07 16:43:55 +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:
+99
-10
@@ -1,14 +1,103 @@
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
if (request.method == "getStatus")
|
||||
sendResponse({status: localStorage});
|
||||
else
|
||||
sendResponse({});
|
||||
const KEYWORDS_KEY = "kf_keywords";
|
||||
const FINDINGS_KEY = "kf_findings";
|
||||
|
||||
const DEFAULT_KEYWORDS = [
|
||||
"key", "api_key", "apikey", "api-key", "secret", "token",
|
||||
"access_token", "auth", "credential", "password",
|
||||
"client_id", "client_secret"
|
||||
];
|
||||
|
||||
chrome.runtime.onInstalled.addListener(async (details) => {
|
||||
if (details.reason === "install") {
|
||||
await chrome.storage.local.set({
|
||||
[KEYWORDS_KEY]: DEFAULT_KEYWORDS,
|
||||
[FINDINGS_KEY]: []
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.type === "finding") {
|
||||
saveFinding(request.data).then(() => sendResponse({ ok: true }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "getKeywords") {
|
||||
getKeywords().then((keywords) => sendResponse({ keywords }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "getFindings") {
|
||||
getFindings().then((findings) => sendResponse({ findings }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "addKeyword") {
|
||||
addKeyword(request.keyword).then((result) => sendResponse(result));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "removeKeyword") {
|
||||
removeKeyword(request.keyword).then(() => sendResponse({ ok: true }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "removeFinding") {
|
||||
removeFinding(request.url).then(() => sendResponse({ ok: true }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "clearFindings") {
|
||||
clearFindings().then(() => sendResponse({ ok: true }));
|
||||
return true;
|
||||
}
|
||||
if (request.type === "exportFindings") {
|
||||
getFindings().then((findings) => sendResponse({ findings }));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function getKeywords() {
|
||||
const result = await chrome.storage.local.get(KEYWORDS_KEY);
|
||||
return result[KEYWORDS_KEY] || DEFAULT_KEYWORDS;
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
if (request.getter)
|
||||
console.log(request.getter);
|
||||
localStorage.setItem(request.getter,request.getter);
|
||||
});
|
||||
async function addKeyword(keyword) {
|
||||
const keywords = await getKeywords();
|
||||
const normalized = keyword.trim().toLowerCase();
|
||||
if (!normalized) return { ok: false, error: "Keyword cannot be empty." };
|
||||
if (keywords.includes(normalized)) return { ok: false, error: "Keyword already exists." };
|
||||
keywords.push(normalized);
|
||||
await chrome.storage.local.set({ [KEYWORDS_KEY]: keywords });
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
async function removeKeyword(keyword) {
|
||||
const keywords = await getKeywords();
|
||||
await chrome.storage.local.set({ [KEYWORDS_KEY]: keywords.filter((k) => k !== keyword) });
|
||||
}
|
||||
|
||||
async function getFindings() {
|
||||
const result = await chrome.storage.local.get(FINDINGS_KEY);
|
||||
return result[FINDINGS_KEY] || [];
|
||||
}
|
||||
|
||||
async function saveFinding(finding) {
|
||||
const findings = await getFindings();
|
||||
const isDuplicate = findings.some(
|
||||
(f) => f.url === finding.url && f.match === finding.match
|
||||
);
|
||||
if (isDuplicate) return;
|
||||
findings.push(finding);
|
||||
await chrome.storage.local.set({ [FINDINGS_KEY]: findings });
|
||||
|
||||
const badgeCount = findings.length;
|
||||
chrome.action.setBadgeText({ text: badgeCount > 0 ? String(badgeCount) : "" });
|
||||
chrome.action.setBadgeBackgroundColor({ color: "#e74c3c" });
|
||||
}
|
||||
|
||||
async function removeFinding(url) {
|
||||
const findings = await getFindings();
|
||||
const updated = findings.filter((f) => f.url !== url);
|
||||
await chrome.storage.local.set({ [FINDINGS_KEY]: updated });
|
||||
chrome.action.setBadgeText({ text: updated.length > 0 ? String(updated.length) : "" });
|
||||
}
|
||||
|
||||
async function clearFindings() {
|
||||
await chrome.storage.local.set({ [FINDINGS_KEY]: [] });
|
||||
chrome.action.setBadgeText({ text: "" });
|
||||
}
|
||||
|
||||
Vendored
-7
File diff suppressed because one or more lines are too long
+314
-40
@@ -1,59 +1,333 @@
|
||||
(async function () {
|
||||
"use strict";
|
||||
|
||||
console.log("keyFinder🔑 is working!");
|
||||
const pageUrl = location.href;
|
||||
const pageDomain = location.hostname;
|
||||
const seen = new Set();
|
||||
|
||||
let keywords = [];
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: "getKeywords" });
|
||||
keywords = (response.keywords || []).map((k) => k.toLowerCase());
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
function shannonEntropy(str) {
|
||||
const len = str.length;
|
||||
if (len === 0) return 0;
|
||||
const freq = {};
|
||||
for (const ch of str) freq[ch] = (freq[ch] || 0) + 1;
|
||||
let entropy = 0;
|
||||
for (const ch in freq) {
|
||||
const p = freq[ch] / len;
|
||||
entropy -= p * Math.log2(p);
|
||||
}
|
||||
return entropy;
|
||||
}
|
||||
|
||||
//custom searches
|
||||
function isHighEntropy(str) {
|
||||
if (str.length < 12) return false;
|
||||
return shannonEntropy(str) > 3.5;
|
||||
}
|
||||
|
||||
let js = document.getElementsByTagName('script');
|
||||
function isFalsePositive(match) {
|
||||
if (!match || match.length < 8) return true;
|
||||
const lower = match.toLowerCase();
|
||||
const fp = [
|
||||
"true", "false", "null", "undefined", "function", "return",
|
||||
"window", "document", "object", "string", "number", "boolean",
|
||||
"prototype", "constructor", "adsbygoogle", "googletag",
|
||||
"use strict", "text/javascript", "application/json",
|
||||
"content-type", "text/html", "text/css", "image/png",
|
||||
"image/jpeg", "charset=utf-8", "viewport", "width=device",
|
||||
"http-equiv", "stylesheet", "text/plain",
|
||||
];
|
||||
for (const f of fp) {
|
||||
if (lower === f) return true;
|
||||
}
|
||||
if (/^(0+|1+|a+|f+|x+)$/i.test(match)) return true;
|
||||
if (/^[a-z]+$/i.test(match) && match.length < 20) return true;
|
||||
if (/^(https?:\/\/)?[a-z0-9.-]+\.(js|css|html|png|jpg|gif|svg|woff|ttf|eot|ico)$/i.test(match)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function report(data) {
|
||||
const key = `${data.type}:${data.match}:${data.url || ""}`;
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
try {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "finding",
|
||||
data: { ...data, domain: pageDomain, pageUrl, timestamp: Date.now() },
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function scanText(text, sourceUrl, sourceType) {
|
||||
if (!text || text.length < 10) return;
|
||||
|
||||
chrome.runtime.sendMessage({method: "getStatus"}, function(response) {
|
||||
|
||||
var savedSearch = response.status;
|
||||
|
||||
for (var key in savedSearch) {
|
||||
|
||||
if (savedSearch.hasOwnProperty(key)) {
|
||||
|
||||
for(src in js){
|
||||
|
||||
var url = js[src].src;
|
||||
let regex = new RegExp(key,'i');
|
||||
|
||||
try {
|
||||
|
||||
if(url.search(regex) !== -1) {
|
||||
chrome.runtime.sendMessage({getter: url}, function(response) {
|
||||
});
|
||||
console.log(`KeyFinder: a potential API key found: ${url} it matched your search ${key}`);
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
for (const pattern of SECRET_PATTERNS) {
|
||||
pattern.re.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = pattern.re.exec(text)) !== null) {
|
||||
const matched = m[1] || m[0];
|
||||
if (isFalsePositive(matched)) continue;
|
||||
report({
|
||||
url: sourceUrl,
|
||||
match: matched.substring(0, 200),
|
||||
type: sourceType,
|
||||
patternName: pattern.name,
|
||||
severity: pattern.severity,
|
||||
confidence: pattern.confidence,
|
||||
provider: pattern.provider,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
for (const kw of keywords) {
|
||||
const escaped = kw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const kwRegex = new RegExp(
|
||||
`(?:${escaped})\\s*[:=]\\s*['"\`]([^'"\`\\n]{8,200})['"\`]`,
|
||||
"gi"
|
||||
);
|
||||
let m;
|
||||
while ((m = kwRegex.exec(text)) !== null) {
|
||||
const val = m[1];
|
||||
if (isFalsePositive(val)) continue;
|
||||
report({
|
||||
url: sourceUrl,
|
||||
match: val.substring(0, 200),
|
||||
type: sourceType,
|
||||
patternName: `Keyword: ${kw}`,
|
||||
severity: "medium",
|
||||
confidence: isHighEntropy(val) ? "high" : "medium",
|
||||
provider: "Keyword Match",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanScriptSrcUrls() {
|
||||
const scripts = document.querySelectorAll("script[src]");
|
||||
for (const script of scripts) {
|
||||
const src = script.src;
|
||||
if (!src) continue;
|
||||
for (const kw of keywords) {
|
||||
if (src.toLowerCase().includes(kw)) {
|
||||
report({
|
||||
url: src, match: src, type: "script-src",
|
||||
patternName: `Script URL contains: ${kw}`,
|
||||
severity: "medium", confidence: "medium", provider: "URL Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
const url = new URL(src);
|
||||
for (const [param, value] of url.searchParams) {
|
||||
if (value.length >= 16 && isHighEntropy(value)) {
|
||||
report({
|
||||
url: src, match: `${param}=${value.substring(0, 100)}`,
|
||||
type: "url-param", patternName: "High-Entropy URL Parameter",
|
||||
severity: "medium", confidence: "medium", provider: "URL Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function scanInlineScripts() {
|
||||
const scripts = document.querySelectorAll("script:not([src])");
|
||||
for (const script of scripts) {
|
||||
scanText(script.textContent, pageUrl, "inline-script");
|
||||
}
|
||||
}
|
||||
|
||||
async function scanExternalScripts() {
|
||||
const scripts = document.querySelectorAll("script[src]");
|
||||
const fetched = new Set();
|
||||
for (const script of scripts) {
|
||||
try {
|
||||
const src = script.src;
|
||||
if (fetched.has(src)) continue;
|
||||
if (new URL(src).origin !== location.origin) continue;
|
||||
fetched.add(src);
|
||||
const resp = await fetch(src, { credentials: "omit" });
|
||||
if (!resp.ok) continue;
|
||||
const text = await resp.text();
|
||||
scanText(text, src, "external-script");
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// for GoogleMaps API key
|
||||
for (src in js) {
|
||||
let url = js[src].src;
|
||||
try {
|
||||
if(url.search(/key/i) !== -1) {
|
||||
console.log(`KeyFinder: a potential API key found: ${url}`)
|
||||
function scanMetaTags() {
|
||||
const metas = document.querySelectorAll("meta");
|
||||
for (const meta of metas) {
|
||||
const content = meta.getAttribute("content");
|
||||
if (!content || content.length < 12) continue;
|
||||
const name = (meta.getAttribute("name") || meta.getAttribute("property") || "").toLowerCase();
|
||||
const sensitive = ["api-key", "api_key", "apikey", "token", "secret", "access-token", "csrf-token", "csrf_token"];
|
||||
if (sensitive.some((s) => name.includes(s))) {
|
||||
report({
|
||||
url: pageUrl, match: `meta[${name}]=${content.substring(0, 100)}`,
|
||||
type: "meta-tag", patternName: "Sensitive Meta Tag",
|
||||
severity: "high", confidence: "high", provider: "DOM Scan",
|
||||
});
|
||||
}
|
||||
scanText(`${name}=${content}`, pageUrl, "meta-tag");
|
||||
}
|
||||
}
|
||||
|
||||
//saving the results
|
||||
chrome.runtime.sendMessage({getter: url}, function(response) {
|
||||
function scanHiddenInputs() {
|
||||
const inputs = document.querySelectorAll('input[type="hidden"]');
|
||||
for (const input of inputs) {
|
||||
const name = (input.name || input.id || "").toLowerCase();
|
||||
const value = input.value;
|
||||
if (!value || value.length < 8) continue;
|
||||
const sensitive = ["token", "csrf", "api_key", "apikey", "secret", "auth", "session", "nonce", "key", "access_token"];
|
||||
if (sensitive.some((s) => name.includes(s)) || isHighEntropy(value)) {
|
||||
report({
|
||||
url: pageUrl, match: `${name}=${value.substring(0, 100)}`,
|
||||
type: "hidden-input", patternName: "Hidden Form Field",
|
||||
severity: isHighEntropy(value) ? "high" : "medium",
|
||||
confidence: sensitive.some((s) => name.includes(s)) ? "high" : "medium",
|
||||
provider: "DOM Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanDataAttributes() {
|
||||
const all = document.querySelectorAll("*");
|
||||
for (const el of all) {
|
||||
for (const attr of el.attributes) {
|
||||
if (!/^data-.*(?:key|token|secret|auth|api|credential|password)/i.test(attr.name)) continue;
|
||||
if (!attr.value || attr.value.length < 8) continue;
|
||||
report({
|
||||
url: pageUrl, match: `${attr.name}="${attr.value.substring(0, 100)}"`,
|
||||
type: "data-attribute", patternName: "Sensitive Data Attribute",
|
||||
severity: "medium", confidence: isHighEntropy(attr.value) ? "high" : "medium",
|
||||
provider: "DOM Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanHtmlComments() {
|
||||
const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_COMMENT, null);
|
||||
while (walker.nextNode()) {
|
||||
const text = walker.currentNode.textContent;
|
||||
if (text && text.length >= 20) {
|
||||
scanText(text, pageUrl, "html-comment");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanLinkHrefs() {
|
||||
const links = document.querySelectorAll("a[href], link[href]");
|
||||
for (const link of links) {
|
||||
try {
|
||||
const href = link.href;
|
||||
if (!href) continue;
|
||||
const url = new URL(href);
|
||||
for (const [param, value] of url.searchParams) {
|
||||
const p = param.toLowerCase();
|
||||
const sensitive = ["key", "api_key", "apikey", "token", "secret", "access_token", "auth", "password", "session_id"];
|
||||
if (sensitive.some((s) => p.includes(s)) && value.length >= 8) {
|
||||
report({
|
||||
url: href, match: `${param}=${value.substring(0, 100)}`,
|
||||
type: "url-param", patternName: "Sensitive URL Parameter",
|
||||
severity: "high", confidence: "high", provider: "URL Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function scanWebStorage() {
|
||||
const stores = [
|
||||
{ store: localStorage, label: "localStorage" },
|
||||
{ store: sessionStorage, label: "sessionStorage" },
|
||||
];
|
||||
for (const { store, label } of stores) {
|
||||
try {
|
||||
for (let i = 0; i < store.length; i++) {
|
||||
const key = store.key(i);
|
||||
const value = store.getItem(key);
|
||||
if (!value || value.length < 12) continue;
|
||||
const kl = key.toLowerCase();
|
||||
const sensitive = ["token", "key", "secret", "auth", "session", "credential", "password", "jwt", "bearer"];
|
||||
if (sensitive.some((s) => kl.includes(s)) || isHighEntropy(value.substring(0, 100))) {
|
||||
report({
|
||||
url: pageUrl, match: `${label}.${key}=${value.substring(0, 120)}`,
|
||||
type: "web-storage", patternName: `${label} Secret`,
|
||||
severity: "high",
|
||||
confidence: sensitive.some((s) => kl.includes(s)) ? "high" : "medium",
|
||||
provider: "Storage Scan",
|
||||
});
|
||||
}
|
||||
scanText(`${key}=${value}`, pageUrl, "web-storage");
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function scanCookies() {
|
||||
try {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (const cookie of cookies) {
|
||||
const [name, ...rest] = cookie.split("=");
|
||||
if (!name) continue;
|
||||
const value = rest.join("=").trim();
|
||||
const n = name.trim().toLowerCase();
|
||||
const sensitive = ["token", "session", "auth", "jwt", "bearer", "api_key", "apikey", "secret", "credential"];
|
||||
if (value && value.length >= 16 && sensitive.some((s) => n.includes(s))) {
|
||||
report({
|
||||
url: pageUrl, match: `cookie:${name.trim()}=${value.substring(0, 80)}`,
|
||||
type: "cookie", patternName: "Sensitive Cookie",
|
||||
severity: "medium", confidence: "medium", provider: "Cookie Scan",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
window.addEventListener("__kf_finding__", (e) => {
|
||||
const data = e.detail;
|
||||
if (!data) return;
|
||||
|
||||
if (data.rawText) {
|
||||
scanText(data.rawText, data.sourceUrl || pageUrl, data.type);
|
||||
if (!data.match) return;
|
||||
}
|
||||
|
||||
if (data.match) {
|
||||
report({
|
||||
url: data.sourceUrl || pageUrl,
|
||||
match: data.match,
|
||||
type: data.type,
|
||||
patternName: data.patternName || data.type,
|
||||
severity: data.severity || "medium",
|
||||
confidence: data.confidence || "medium",
|
||||
provider: data.provider || "Runtime Scan",
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
//console.log(err)
|
||||
});
|
||||
|
||||
scanScriptSrcUrls();
|
||||
scanInlineScripts();
|
||||
scanMetaTags();
|
||||
scanHiddenInputs();
|
||||
scanDataAttributes();
|
||||
scanHtmlComments();
|
||||
scanLinkHrefs();
|
||||
scanWebStorage();
|
||||
scanCookies();
|
||||
await scanExternalScripts();
|
||||
|
||||
if (seen.size > 0) {
|
||||
console.log(`[KeyFinder] ${seen.size} potential secret(s) found on ${pageDomain}`);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const EVENT_NAME = "__kf_finding__";
|
||||
|
||||
function emit(data) {
|
||||
window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: data }));
|
||||
}
|
||||
|
||||
function shannonEntropy(str) {
|
||||
const len = str.length;
|
||||
if (len === 0) return 0;
|
||||
const freq = {};
|
||||
for (const ch of str) freq[ch] = (freq[ch] || 0) + 1;
|
||||
let entropy = 0;
|
||||
for (const ch in freq) {
|
||||
const p = freq[ch] / len;
|
||||
entropy -= p * Math.log2(p);
|
||||
}
|
||||
return entropy;
|
||||
}
|
||||
|
||||
function isHighEntropy(str) {
|
||||
return str.length >= 12 && shannonEntropy(str) > 3.5;
|
||||
}
|
||||
|
||||
const globalNames = [
|
||||
"API_KEY", "api_key", "apiKey", "apikey",
|
||||
"SECRET", "secret", "secretKey", "secret_key",
|
||||
"TOKEN", "token", "accessToken", "access_token",
|
||||
"AUTH_TOKEN", "authToken", "auth_token",
|
||||
"STRIPE_KEY", "stripeKey", "stripe_key",
|
||||
"FIREBASE_CONFIG", "firebaseConfig",
|
||||
"AWS_ACCESS_KEY", "awsAccessKey",
|
||||
"__NEXT_DATA__", "__NUXT__", "__APP_CONFIG__",
|
||||
"__ENV__", "__CONFIG__", "ENV", "CONFIG",
|
||||
];
|
||||
|
||||
for (const name of globalNames) {
|
||||
try {
|
||||
const val = window[name];
|
||||
if (val === undefined || val === null) continue;
|
||||
const str = typeof val === "object" ? JSON.stringify(val) : String(val);
|
||||
if (str.length < 8 || str === "[object Object]") continue;
|
||||
emit({
|
||||
match: `window.${name}=${str.substring(0, 200)}`,
|
||||
type: "window-global",
|
||||
patternName: "Exposed Global Variable",
|
||||
severity: "high",
|
||||
confidence: typeof val !== "object" && isHighEntropy(str.substring(0, 60)) ? "high" : "medium",
|
||||
provider: "JS Global Scan",
|
||||
isObject: typeof val === "object",
|
||||
rawText: typeof val === "object" ? str.substring(0, 5000) : null,
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const origXhrOpen = XMLHttpRequest.prototype.open;
|
||||
const origXhrSend = XMLHttpRequest.prototype.send;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (method, url) {
|
||||
this._kfUrl = url;
|
||||
return origXhrOpen.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function () {
|
||||
this.addEventListener("load", function () {
|
||||
try {
|
||||
const ct = this.getResponseHeader("content-type") || "";
|
||||
if (ct.includes("json") || ct.includes("javascript") || ct.includes("text")) {
|
||||
const body = this.responseText;
|
||||
if (body && body.length > 10 && body.length < 500000) {
|
||||
emit({
|
||||
type: "xhr-response",
|
||||
sourceUrl: String(this._kfUrl || ""),
|
||||
rawText: body,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
return origXhrSend.apply(this, arguments);
|
||||
};
|
||||
|
||||
const origFetch = window.fetch;
|
||||
window.fetch = async function () {
|
||||
const response = await origFetch.apply(this, arguments);
|
||||
try {
|
||||
const url = typeof arguments[0] === "string" ? arguments[0] : arguments[0]?.url || "";
|
||||
const cloned = response.clone();
|
||||
const ct = cloned.headers.get("content-type") || "";
|
||||
if (ct.includes("json") || ct.includes("javascript") || ct.includes("text")) {
|
||||
cloned.text().then((body) => {
|
||||
if (body && body.length > 10 && body.length < 500000) {
|
||||
emit({
|
||||
type: "fetch-response",
|
||||
sourceUrl: String(url || ""),
|
||||
rawText: body,
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
} catch {}
|
||||
return response;
|
||||
};
|
||||
})();
|
||||
Vendored
-2
File diff suppressed because one or more lines are too long
+134
@@ -0,0 +1,134 @@
|
||||
const SECRET_PATTERNS = [
|
||||
|
||||
{ name: "AWS Access Key ID", re: /\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b/g, severity: "critical", confidence: "high", provider: "AWS" },
|
||||
{ name: "AWS Secret Access Key", re: /(?:aws_secret_access_key|aws_secret|secret_access_key|AWS_SECRET)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi, severity: "critical", confidence: "high", provider: "AWS" },
|
||||
{ name: "AWS MWS Auth Token", re: /amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, severity: "critical", confidence: "high", provider: "AWS" },
|
||||
{ name: "AWS Cognito Pool ID", re: /(?:us|eu|ap|sa|ca|me|af)-(?:east|west|south|north|central|southeast|northeast)-[0-9]:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, severity: "medium", confidence: "high", provider: "AWS" },
|
||||
{ name: "AWS AppSync GraphQL Key", re: /da2-[a-z0-9]{26}/g, severity: "high", confidence: "high", provider: "AWS" },
|
||||
{ name: "AWS Session Token", re: /(?:aws_session_token|AWS_SESSION_TOKEN)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{100,})['"]?/gi, severity: "critical", confidence: "high", provider: "AWS" },
|
||||
|
||||
{ name: "Google API Key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g, severity: "high", confidence: "high", provider: "Google" },
|
||||
{ name: "Google OAuth Access Token", re: /\bya29\.[0-9A-Za-z_-]+/g, severity: "critical", confidence: "high", provider: "Google" },
|
||||
{ name: "Google OAuth Client ID", re: /[0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com/g, severity: "medium", confidence: "high", provider: "Google" },
|
||||
{ name: "Google OAuth Client Secret", re: /(?:client_secret|google_secret)\s*[:=]\s*['"]?(GOCSPX-[A-Za-z0-9_-]{28})['"]?/gi, severity: "critical", confidence: "high", provider: "Google" },
|
||||
{ name: "Google Cloud Service Account", re: /"type"\s*:\s*"service_account"/g, severity: "critical", confidence: "high", provider: "Google" },
|
||||
{ name: "Firebase Database URL", re: /https:\/\/[a-z0-9-]+\.firebaseio\.com/g, severity: "medium", confidence: "high", provider: "Firebase" },
|
||||
{ name: "Firebase Cloud Messaging Key", re: /\bAAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}/g, severity: "high", confidence: "high", provider: "Firebase" },
|
||||
|
||||
{ name: "Azure Storage Account Key", re: /(?:AccountKey|azure_storage_key|AZURE_STORAGE_KEY)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{88})['"]?/gi, severity: "critical", confidence: "high", provider: "Azure" },
|
||||
{ name: "Azure Connection String", re: /DefaultEndpointsProtocol=https?;AccountName=[^;]+;AccountKey=[A-Za-z0-9/+=]{88}/g, severity: "critical", confidence: "high", provider: "Azure" },
|
||||
{ name: "Azure SAS Token", re: /[?&]sig=[A-Za-z0-9%/+=]+&/g, severity: "high", confidence: "medium", provider: "Azure" },
|
||||
|
||||
{ name: "GitHub Personal Access Token", re: /\bghp_[A-Za-z0-9_]{36,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
{ name: "GitHub OAuth Access Token", re: /\bgho_[A-Za-z0-9_]{36,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
{ name: "GitHub User-to-Server Token", re: /\bghu_[A-Za-z0-9_]{36,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
{ name: "GitHub Server-to-Server Token", re: /\bghs_[A-Za-z0-9_]{36,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
{ name: "GitHub Refresh Token", re: /\bghr_[A-Za-z0-9_]{36,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
{ name: "GitHub Fine-grained PAT", re: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g, severity: "critical", confidence: "high", provider: "GitHub" },
|
||||
|
||||
{ name: "GitLab Personal Access Token", re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g, severity: "critical", confidence: "high", provider: "GitLab" },
|
||||
{ name: "GitLab Pipeline Token", re: /\bglptt-[A-Za-z0-9_-]{20,}\b/g, severity: "high", confidence: "high", provider: "GitLab" },
|
||||
{ name: "GitLab Runner Token", re: /\bGR1348941[A-Za-z0-9_-]{20}\b/g, severity: "high", confidence: "high", provider: "GitLab" },
|
||||
|
||||
{ name: "Stripe Secret Key", re: /\bsk_(live|test)_[0-9a-zA-Z]{24,}\b/g, severity: "critical", confidence: "high", provider: "Stripe" },
|
||||
{ name: "Stripe Publishable Key", re: /\bpk_(live|test)_[0-9a-zA-Z]{24,}\b/g, severity: "low", confidence: "high", provider: "Stripe" },
|
||||
{ name: "Stripe Restricted Key", re: /\brk_(live|test)_[0-9a-zA-Z]{24,}\b/g, severity: "critical", confidence: "high", provider: "Stripe" },
|
||||
{ name: "Stripe Webhook Secret", re: /\bwhsec_[A-Za-z0-9]{32,}\b/g, severity: "high", confidence: "high", provider: "Stripe" },
|
||||
|
||||
{ name: "PayPal Braintree Access Token", re: /access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}/g, severity: "critical", confidence: "high", provider: "PayPal" },
|
||||
|
||||
{ name: "Square Access Token", re: /\bsq0atp-[0-9A-Za-z_-]{22}\b/g, severity: "critical", confidence: "high", provider: "Square" },
|
||||
{ name: "Square OAuth Secret", re: /\bsq0csp-[0-9A-Za-z_-]{43}\b/g, severity: "critical", confidence: "high", provider: "Square" },
|
||||
|
||||
{ name: "Slack Bot Token", re: /\bxoxb-[0-9]{10,}-[0-9]{10,}-[A-Za-z0-9]{24,}\b/g, severity: "critical", confidence: "high", provider: "Slack" },
|
||||
{ name: "Slack User Token", re: /\bxoxp-[0-9]{10,}-[0-9]{10,}-[A-Za-z0-9]{24,}\b/g, severity: "critical", confidence: "high", provider: "Slack" },
|
||||
{ name: "Slack App Token", re: /\bxapp-[0-9]+-[A-Za-z0-9]+-[0-9]+-[A-Za-z0-9]+/g, severity: "high", confidence: "high", provider: "Slack" },
|
||||
{ name: "Slack Webhook URL", re: /hooks\.slack\.com\/services\/T[A-Z0-9]{8,}\/B[A-Z0-9]{8,}\/[A-Za-z0-9]{24}/g, severity: "high", confidence: "high", provider: "Slack" },
|
||||
|
||||
{ name: "Discord Bot Token", re: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27,}/g, severity: "critical", confidence: "high", provider: "Discord" },
|
||||
{ name: "Discord Webhook URL", re: /discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g, severity: "high", confidence: "high", provider: "Discord" },
|
||||
|
||||
{ name: "Telegram Bot Token", re: /\b[0-9]{8,10}:[A-Za-z0-9_-]{35}\b/g, severity: "critical", confidence: "high", provider: "Telegram" },
|
||||
|
||||
{ name: "Twilio Account SID", re: /\bAC[0-9a-fA-F]{32}\b/g, severity: "medium", confidence: "high", provider: "Twilio" },
|
||||
{ name: "Twilio API Key", re: /\bSK[0-9a-fA-F]{32}\b/g, severity: "high", confidence: "high", provider: "Twilio" },
|
||||
|
||||
{ name: "SendGrid API Key", re: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g, severity: "critical", confidence: "high", provider: "SendGrid" },
|
||||
|
||||
{ name: "Mailchimp API Key", re: /\b[0-9a-f]{32}-us[0-9]{1,2}\b/g, severity: "high", confidence: "high", provider: "Mailchimp" },
|
||||
|
||||
{ name: "Mailgun API Key", re: /\bkey-[0-9a-zA-Z]{32}\b/g, severity: "high", confidence: "high", provider: "Mailgun" },
|
||||
|
||||
{ name: "NPM Access Token", re: /\bnpm_[A-Za-z0-9]{36}\b/g, severity: "critical", confidence: "high", provider: "NPM" },
|
||||
|
||||
{ name: "Docker Hub Token", re: /\bdckr_pat_[A-Za-z0-9_-]{27}\b/g, severity: "high", confidence: "high", provider: "Docker" },
|
||||
|
||||
{ name: "Vault Token", re: /\b(?:hvs|hvb|hvr)\.[A-Za-z0-9_-]{24,}\b/g, severity: "critical", confidence: "high", provider: "HashiCorp" },
|
||||
{ name: "Terraform Cloud Token", re: /\b[A-Za-z0-9]{14}\.atlasv1\.[A-Za-z0-9_-]{60,}\b/g, severity: "critical", confidence: "high", provider: "HashiCorp" },
|
||||
|
||||
{ name: "MongoDB Connection String", re: /mongodb(?:\+srv)?:\/\/[^\s'"<>]+/g, severity: "critical", confidence: "high", provider: "MongoDB" },
|
||||
{ name: "PostgreSQL Connection String", re: /postgres(?:ql)?:\/\/[^\s'"<>]+/g, severity: "critical", confidence: "high", provider: "PostgreSQL" },
|
||||
{ name: "MySQL Connection String", re: /mysql:\/\/[^\s'"<>]+/g, severity: "critical", confidence: "high", provider: "MySQL" },
|
||||
{ name: "Redis Connection String", re: /redis(?:s)?:\/\/[^\s'"<>]+/g, severity: "critical", confidence: "high", provider: "Redis" },
|
||||
|
||||
{ name: "Shopify Access Token", re: /\bshpat_[a-fA-F0-9]{32}\b/g, severity: "critical", confidence: "high", provider: "Shopify" },
|
||||
{ name: "Shopify Custom App Token", re: /\bshpca_[a-fA-F0-9]{32}\b/g, severity: "critical", confidence: "high", provider: "Shopify" },
|
||||
{ name: "Shopify Private App Token", re: /\bshppa_[a-fA-F0-9]{32}\b/g, severity: "critical", confidence: "high", provider: "Shopify" },
|
||||
{ name: "Shopify Shared Secret", re: /\bshpss_[a-fA-F0-9]{32}\b/g, severity: "critical", confidence: "high", provider: "Shopify" },
|
||||
|
||||
{ name: "Sentry DSN", re: /https:\/\/[0-9a-f]{32}@(?:o[0-9]+\.)?(?:sentry\.io|[a-z0-9.-]+)\/[0-9]+/g, severity: "medium", confidence: "high", provider: "Sentry" },
|
||||
{ name: "Sentry Auth Token", re: /\bsntrys_[A-Za-z0-9_]{64,}\b/g, severity: "high", confidence: "high", provider: "Sentry" },
|
||||
|
||||
{ name: "New Relic API Key", re: /\bNRAK-[A-Z0-9]{27}\b/g, severity: "high", confidence: "high", provider: "New Relic" },
|
||||
{ name: "New Relic Browser Key", re: /\bNRJS-[a-f0-9]{19}\b/g, severity: "medium", confidence: "high", provider: "New Relic" },
|
||||
|
||||
{ name: "PlanetScale Token", re: /\bpscale_tkn_[A-Za-z0-9_-]{43}\b/g, severity: "critical", confidence: "high", provider: "PlanetScale" },
|
||||
{ name: "PlanetScale Password", re: /\bpscale_pw_[A-Za-z0-9_-]{43}\b/g, severity: "critical", confidence: "high", provider: "PlanetScale" },
|
||||
|
||||
{ name: "Linear API Key", re: /\blin_api_[A-Za-z0-9]{40}\b/g, severity: "high", confidence: "high", provider: "Linear" },
|
||||
|
||||
{ name: "Notion Integration Token", re: /\bntn_[A-Za-z0-9]{40,}\b/g, severity: "high", confidence: "high", provider: "Notion" },
|
||||
{ name: "Notion Secret", re: /\bsecret_[A-Za-z0-9]{43}\b/g, severity: "high", confidence: "medium", provider: "Notion" },
|
||||
|
||||
{ name: "OpenAI API Key", re: /\bsk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}\b/g, severity: "critical", confidence: "high", provider: "OpenAI" },
|
||||
{ name: "OpenAI API Key (Project)", re: /\bsk-proj-[A-Za-z0-9_-]{40,}\b/g, severity: "critical", confidence: "high", provider: "OpenAI" },
|
||||
{ name: "Anthropic API Key", re: /\bsk-ant-[A-Za-z0-9_-]{90,}\b/g, severity: "critical", confidence: "high", provider: "Anthropic" },
|
||||
{ name: "HuggingFace Token", re: /\bhf_[A-Za-z0-9]{34}\b/g, severity: "high", confidence: "high", provider: "HuggingFace" },
|
||||
{ name: "Replicate API Token", re: /\br8_[A-Za-z0-9]{36}\b/g, severity: "high", confidence: "high", provider: "Replicate" },
|
||||
|
||||
{ name: "Twitter Bearer Token", re: /\bAAAAAAAAAAAAAAAAAAAAA[A-Za-z0-9%]+/g, severity: "critical", confidence: "high", provider: "Twitter" },
|
||||
{ name: "Facebook Access Token", re: /\bEAAC[a-zA-Z0-9]+/g, severity: "critical", confidence: "high", provider: "Facebook" },
|
||||
{ name: "Instagram Access Token", re: /\bIGQV[A-Za-z0-9_-]+/g, severity: "high", confidence: "high", provider: "Instagram" },
|
||||
|
||||
{ name: "Cloudflare API Token", re: /(?:cloudflare_api_token|CF_API_TOKEN|CLOUDFLARE_API_TOKEN)\s*[:=]\s*['"]?([A-Za-z0-9_-]{40})['"]?/gi, severity: "high", confidence: "medium", provider: "Cloudflare" },
|
||||
{ name: "DigitalOcean Token", re: /\bdop_v1_[a-f0-9]{64}\b/g, severity: "critical", confidence: "high", provider: "DigitalOcean" },
|
||||
{ name: "DigitalOcean Spaces Key", re: /\bDO00[A-Z0-9]{36}\b/g, severity: "high", confidence: "high", provider: "DigitalOcean" },
|
||||
{ name: "Doppler Token", re: /\bdp\.(?:ct|st|sa|scim)\.[A-Za-z0-9_-]{40,}\b/g, severity: "critical", confidence: "high", provider: "Doppler" },
|
||||
{ name: "Pulumi Access Token", re: /\bpul-[a-f0-9]{40}\b/g, severity: "high", confidence: "high", provider: "Pulumi" },
|
||||
{ name: "Grafana API Key", re: /\bglc_[A-Za-z0-9_+/]{32,}\b/g, severity: "high", confidence: "high", provider: "Grafana" },
|
||||
{ name: "Grafana Service Account Token", re: /\bglsa_[A-Za-z0-9_]{32,}_[0-9a-f]{8}\b/g, severity: "high", confidence: "high", provider: "Grafana" },
|
||||
|
||||
{ name: "Mapbox Public Token", re: /\bpk\.[A-Za-z0-9_-]{60,}\.[A-Za-z0-9_-]{20,}\b/g, severity: "medium", confidence: "high", provider: "Mapbox" },
|
||||
{ name: "Mapbox Secret Token", re: /\bsk\.[A-Za-z0-9_-]{60,}\.[A-Za-z0-9_-]{20,}\b/g, severity: "high", confidence: "high", provider: "Mapbox" },
|
||||
|
||||
{ name: "Datadog API Key", re: /(?:datadog_api_key|DD_API_KEY|DATADOG_API_KEY)\s*[:=]\s*['"]?([0-9a-f]{32})['"]?/gi, severity: "high", confidence: "high", provider: "Datadog" },
|
||||
|
||||
{ name: "Algolia API Key", re: /(?:algolia_api_key|ALGOLIA_API_KEY|algolia_admin_key)\s*[:=]\s*['"]?([A-Za-z0-9]{32})['"]?/gi, severity: "high", confidence: "medium", provider: "Algolia" },
|
||||
|
||||
{ name: "Vercel Access Token", re: /(?:vercel_token|VERCEL_TOKEN)\s*[:=]\s*['"]?([A-Za-z0-9]{24})['"]?/gi, severity: "high", confidence: "medium", provider: "Vercel" },
|
||||
|
||||
{ name: "JSON Web Token", re: /\beyJhbGci[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, severity: "high", confidence: "high", provider: "JWT" },
|
||||
|
||||
{ name: "RSA Private Key", re: /-----BEGIN RSA PRIVATE KEY-----/g, severity: "critical", confidence: "high", provider: "Crypto" },
|
||||
{ name: "EC Private Key", re: /-----BEGIN EC PRIVATE KEY-----/g, severity: "critical", confidence: "high", provider: "Crypto" },
|
||||
{ name: "OpenSSH Private Key", re: /-----BEGIN OPENSSH PRIVATE KEY-----/g, severity: "critical", confidence: "high", provider: "Crypto" },
|
||||
{ name: "PGP Private Key Block", re: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g, severity: "critical", confidence: "high", provider: "Crypto" },
|
||||
|
||||
{ name: "Authorization Bearer Token", re: /(?:Authorization|Bearer)\s*[:=]\s*['"]?Bearer\s+([A-Za-z0-9_.\-/+=]{20,})['"]?/gi, severity: "high", confidence: "medium", provider: "Generic" },
|
||||
{ name: "Basic Auth Credentials", re: /(?:Authorization)\s*[:=]\s*['"]?Basic\s+([A-Za-z0-9+/=]{10,})['"]?/gi, severity: "high", confidence: "medium", provider: "Generic" },
|
||||
{ name: "Generic API Key", re: /(?:api[_-]?key|apiKey|API_KEY)\s*[:=]\s*['"`]([A-Za-z0-9_\-/.]{16,120})['"`]/gi, severity: "medium", confidence: "medium", provider: "Generic" },
|
||||
{ name: "Generic Secret", re: /(?:secret[_-]?key|secretKey|SECRET_KEY|app[_-]?secret|APP_SECRET)\s*[:=]\s*['"`]([A-Za-z0-9_\-/.]{16,120})['"`]/gi, severity: "high", confidence: "medium", provider: "Generic" },
|
||||
{ name: "Generic Token", re: /(?:access[_-]?token|auth[_-]?token|AUTH_TOKEN|ACCESS_TOKEN)\s*[:=]\s*['"`]([A-Za-z0-9_\-/.]{16,120})['"`]/gi, severity: "high", confidence: "medium", provider: "Generic" },
|
||||
{ name: "Generic Password", re: /(?:password|passwd|PASSWD|PASSWORD)\s*[:=]\s*['"`]([^\s'"`]{8,120})['"`]/gi, severity: "high", confidence: "low", provider: "Generic" },
|
||||
{ name: "Credential URL", re: /(?:https?|ftp):\/\/[^\s:@'"]+:[^\s:@'"]+@[^\s'"]+/g, severity: "high", confidence: "medium", provider: "Generic" },
|
||||
];
|
||||
Vendored
-5
File diff suppressed because one or more lines are too long
+74
-40
@@ -1,48 +1,82 @@
|
||||
var save = document.getElementById('save');
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
|
||||
|
||||
save.onclick = function() {
|
||||
|
||||
var keyword = document.getElementById("keyword").value;
|
||||
|
||||
|
||||
if(keyword == "key") {
|
||||
alert("already exist!");
|
||||
|
||||
}
|
||||
if(keyword === ""){
|
||||
alert("cannot be empty");
|
||||
}
|
||||
|
||||
if(keyword == localStorage.getItem(keyword)){
|
||||
alert("you can't add the same word twice!")
|
||||
}
|
||||
else
|
||||
{
|
||||
keyword.trim();
|
||||
//setting key value to keyword for easy lopping
|
||||
localStorage.setItem(keyword,keyword);
|
||||
|
||||
}
|
||||
async function init() {
|
||||
await renderKeywords();
|
||||
await renderStats();
|
||||
document.getElementById("keywordForm").addEventListener("submit", handleAddKeyword);
|
||||
}
|
||||
|
||||
async function renderKeywords() {
|
||||
const response = await chrome.runtime.sendMessage({ type: "getKeywords" });
|
||||
const keywords = response.keywords || [];
|
||||
const list = document.getElementById("keywordList");
|
||||
list.innerHTML = "";
|
||||
|
||||
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))){
|
||||
if(localStorage.key(i) !== 'undefined')
|
||||
{
|
||||
$('body').append(`<li>${localStorage.key(i)}<button value=${localStorage.key(i)} class="btn"><i class="fa fa-trash"></i></button></li>`)
|
||||
}
|
||||
document.getElementById("keywordCount").textContent = keywords.length;
|
||||
|
||||
if (keywords.length === 0) {
|
||||
list.innerHTML = '<li class="empty-state">No keywords configured</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const kw of keywords) {
|
||||
const li = document.createElement("li");
|
||||
li.className = "keyword-item";
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "keyword-label";
|
||||
label.textContent = kw;
|
||||
|
||||
const removeBtn = document.createElement("button");
|
||||
removeBtn.className = "keyword-remove";
|
||||
removeBtn.textContent = "\u00D7";
|
||||
removeBtn.title = `Remove "${kw}"`;
|
||||
removeBtn.addEventListener("click", () => handleRemoveKeyword(kw));
|
||||
|
||||
li.appendChild(label);
|
||||
li.appendChild(removeBtn);
|
||||
list.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderStats() {
|
||||
const response = await chrome.runtime.sendMessage({ type: "getFindings" });
|
||||
const findings = response.findings || [];
|
||||
document.getElementById("findingCount").textContent = findings.length;
|
||||
}
|
||||
|
||||
$('.btn').click(function() {
|
||||
var selectedItem = $(this).val();
|
||||
localStorage.removeItem(selectedItem);
|
||||
location.reload();
|
||||
alert(`${selectedItem} removed`);
|
||||
})
|
||||
async function handleAddKeyword(e) {
|
||||
e.preventDefault();
|
||||
const input = document.getElementById("keywordInput");
|
||||
const errorMsg = document.getElementById("errorMsg");
|
||||
const keyword = input.value.trim();
|
||||
|
||||
errorMsg.hidden = true;
|
||||
|
||||
if (!keyword) {
|
||||
showError("Keyword cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await chrome.runtime.sendMessage({ type: "addKeyword", keyword });
|
||||
|
||||
if (!result.ok) {
|
||||
showError(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = "";
|
||||
await renderKeywords();
|
||||
}
|
||||
|
||||
async function handleRemoveKeyword(keyword) {
|
||||
await chrome.runtime.sendMessage({ type: "removeKeyword", keyword });
|
||||
await renderKeywords();
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
const errorMsg = document.getElementById("errorMsg");
|
||||
errorMsg.textContent = msg;
|
||||
errorMsg.hidden = false;
|
||||
setTimeout(() => { errorMsg.hidden = true; }, 3000);
|
||||
}
|
||||
|
||||
+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