From 7b5aad4864b13126a3df4b3132c1d24633de6627 Mon Sep 17 00:00:00 2001 From: moamen Date: Fri, 10 Apr 2026 07:08:09 +0200 Subject: [PATCH] Add GitHub Pages landing page --- .gitignore | 10 - LICENSE | 21 - PRIVACY.md | 61 --- README.md | 135 ------ css/popup.css | 240 ---------- css/results.css | 341 -------------- icons/banner.png | Bin 59975 -> 0 bytes icons/icon128.png | Bin 3824 -> 0 bytes icons/icon16.png | Bin 362 -> 0 bytes icons/icon48.png | Bin 1121 -> 0 bytes icons/logo-512.png | Bin 11510 -> 0 bytes icons/social-preview.png | Bin 59975 -> 0 bytes index.html | 981 +++++++++++++++++++++++++++++++++++++++ js/background.js | 103 ---- js/content.js | 333 ------------- js/interceptor.js | 106 ----- js/patterns.js | 134 ------ js/popup.js | 82 ---- js/results.js | 249 ---------- manifest.json | 38 -- popup.html | 54 --- results.html | 68 --- 22 files changed, 981 insertions(+), 1975 deletions(-) delete mode 100644 .gitignore delete mode 100644 LICENSE delete mode 100644 PRIVACY.md delete mode 100644 README.md delete mode 100644 css/popup.css delete mode 100644 css/results.css delete mode 100644 icons/banner.png delete mode 100644 icons/icon128.png delete mode 100644 icons/icon16.png delete mode 100644 icons/icon48.png delete mode 100644 icons/logo-512.png delete mode 100644 icons/social-preview.png create mode 100644 index.html delete mode 100644 js/background.js delete mode 100644 js/content.js delete mode 100644 js/interceptor.js delete mode 100644 js/patterns.js delete mode 100644 js/popup.js delete mode 100644 js/results.js delete mode 100644 manifest.json delete mode 100644 popup.html delete mode 100644 results.html diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b50ae36..0000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -*.crx -*.pem -*.zip -.idea/ -.vscode/ -*.swp -*.swo -*~ -.claude/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 604ebff..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Mo'men Basel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/PRIVACY.md b/PRIVACY.md deleted file mode 100644 index c0a4a3d..0000000 --- a/PRIVACY.md +++ /dev/null @@ -1,61 +0,0 @@ -# Privacy Policy - KeyFinder Chrome Extension - -**Last Updated:** April 7, 2026 - -## Overview - -KeyFinder is a browser extension that scans web pages for leaked API keys, tokens, and secrets. This privacy policy explains how the extension handles data. - -## Data Collection - -KeyFinder does **not** collect, transmit, or share any personal data or browsing data with external servers. The extension operates entirely on your local device. - -## What Data is Stored Locally - -The extension stores the following data locally on your device using Chrome's built-in storage API (`chrome.storage.local`): - -- **Search keywords**: User-configured keywords used to identify potential secrets on web pages. -- **Scan findings**: When a potential secret is detected, the extension stores the pattern name, severity level, matched value, page URL, and domain where it was found. - -This data never leaves your device. - -## How Data is Processed - -- All page scanning happens locally within your browser. -- The extension reads page content (scripts, meta tags, form fields, HTML comments, browser storage, and network responses) to match against known secret patterns. -- No page content, scan results, or browsing activity is sent to any external server, API, or third party. - -## Data Sharing - -KeyFinder does **not** share any data with third parties. Specifically: - -- No data is sold to third parties. -- No data is used for advertising or marketing purposes. -- No data is transferred to third parties for reasons unrelated to the extension's core functionality. -- No analytics, telemetry, or tracking is implemented. - -## Data Retention - -All stored data remains on your local device until you choose to delete it. You can clear all findings at any time using the "Clear All" button in the findings dashboard. Uninstalling the extension removes all stored data. - -## Permissions - -- **activeTab**: Used to access the current page's content for scanning when the extension is active. -- **storage**: Used to save your keyword preferences and scan findings locally. -- **Host permissions**: The extension runs on all URLs because leaked secrets can appear on any website. No data from these pages is transmitted externally. - -## Third-Party Services - -KeyFinder does not integrate with, connect to, or send data to any third-party services. - -## Changes to This Policy - -Any changes to this privacy policy will be reflected in the extension's GitHub repository and the "Last Updated" date above. - -## Contact - -If you have questions about this privacy policy, contact the developer: - -- GitHub: [github.com/momenbasel](https://github.com/momenbasel) -- X: [@momenbassel](https://x.com/momenbassel) -- LinkedIn: [linkedin.com/in/momenbasel](https://www.linkedin.com/in/momenbasel/) diff --git a/README.md b/README.md deleted file mode 100644 index 0308118..0000000 --- a/README.md +++ /dev/null @@ -1,135 +0,0 @@ -

- KeyFinder logo -

- -

KeyFinder

- -

- Passive API key and secret discovery for Chrome -

- -

- - - - - -

- -
- -KeyFinder is a Chrome extension that passively scans every page you visit for leaked API keys, tokens, secrets, and credentials. It runs silently in the background with zero configuration required. - -## What It Detects - -KeyFinder ships with **80+ detection patterns** covering secrets from: - -| Category | Providers | -|----------|-----------| -| **Cloud** | AWS (Access Keys, Secret Keys, Session Tokens, Cognito), Google Cloud (API Keys, OAuth, Service Accounts), Azure (Storage Keys, SAS Tokens, Connection Strings) | -| **Source Control** | GitHub (PATs, OAuth, Fine-grained tokens), GitLab (PATs, Pipeline, Runner tokens), Bitbucket | -| **Payments** | Stripe (Secret, Publishable, Restricted, Webhook), PayPal Braintree, Square | -| **Communication** | Slack (Bot, User, App tokens, Webhooks), Discord (Bot tokens, Webhooks), Telegram, Twilio, SendGrid | -| **AI / ML** | OpenAI, Anthropic, HuggingFace, Replicate | -| **Databases** | MongoDB, PostgreSQL, MySQL, Redis connection strings | -| **SaaS** | Shopify, Sentry, New Relic, PlanetScale, Linear, Notion, Datadog, Algolia, Mapbox | -| **Infrastructure** | HashiCorp Vault, Terraform, Docker Hub, NPM, Cloudflare, DigitalOcean, Doppler, Pulumi, Grafana | -| **Crypto** | RSA, EC, OpenSSH, PGP, DSA private keys | -| **Generic** | JWTs, Bearer tokens, Basic Auth, API key assignments, credential URLs, high-entropy strings | - -## How It Works - -KeyFinder scans **10 different attack surfaces** on every page: - -1. **Script `src` URLs** - Checks all script source URLs for keywords and tokens in query parameters -2. **Inline scripts** - Scans ` + + + + + + + + + +
+
+
+ 558+ stars Trusted by security researchers +
+

+ Find leaked secrets,
+ passively. +

+

+ A Chrome extension that scans every page you visit for API keys, tokens, and credentials. + 80+ patterns. 10 attack surfaces. Zero config. +

+ + + +
+
+ + Script URLs +
+
+ + Inline Scripts +
+
+ + External JS +
+
+ + Meta Tags +
+
+ + Hidden Fields +
+
+ + Data Attrs +
+
+ + Comments +
+
+ + URL Params +
+
+ + Web Storage +
+
+ + Network +
+
+
+
+ + +
+ +

Powerful detection, zero friction

+

Install once. Every page you visit is automatically scanned for leaked secrets across all attack surfaces.

+
+
+
+ +
+

80+ Detection Patterns

+

Covers cloud providers, payment platforms, communication tools, AI services, databases, SaaS, infrastructure keys, crypto secrets, and generic patterns.

+
+
+
+ +
+

10 Attack Surfaces

+

Scans script URLs, inline scripts, external JS, meta tags, hidden form fields, data attributes, HTML comments, URL parameters, web storage, and network responses.

+
+
+
+ +
+

Shannon Entropy Analysis

+

Calculates Shannon entropy for detected strings. High-entropy values get flagged as likely secrets, reducing false positives on random-looking tokens.

+
+
+
+ +
+

Zero Dependencies

+

Pure vanilla JavaScript. No external libraries, no build step, no framework. Lightweight, fast, and auditable. Just the extension code and nothing else.

+
+
+
+ +
+

Manifest V3

+

Built on the latest Chrome extension architecture with a service worker. Future-proof, secure, and compatible with all modern Chromium browsers.

+
+
+
+ +
+

Export and Report

+

Professional dashboard with filtering, sorting, and search. Export findings as JSON or CSV. Badge counter on the extension icon shows live results.

+
+
+ +
+
+
80+
+
Detection Patterns
+
+
+
10
+
Attack Surfaces
+
+
+
558+
+
GitHub Stars
+
+
+
0
+
Dependencies
+
+
+
+ + +
+ +

Secrets across every category

+

From cloud provider keys to cryptocurrency wallet seeds, keyFinder recognizes credentials across the entire modern stack.

+
+
+

Cloud

+
+ AWS + GCP + Azure + DigitalOcean + Heroku +
+
+
+

Source Control

+
+ GitHub + GitLab + Bitbucket +
+
+
+

Payments

+
+ Stripe + PayPal + Square + Braintree +
+
+
+

Communication

+
+ Slack + Discord + Telegram + Twilio + SendGrid +
+
+
+

AI / ML

+
+ OpenAI + Anthropic + Cohere + HuggingFace +
+
+
+

Databases

+
+ MongoDB + PostgreSQL + Redis + MySQL +
+
+
+

SaaS

+
+ Mailchimp + Algolia + Firebase + Shopify + Zendesk +
+
+
+

Infrastructure

+
+ Docker + Kubernetes + Terraform + Vault + NPM +
+
+
+

Crypto

+
+ Private Keys + Seed Phrases + Wallet Keys + JWT Secrets +
+
+
+

Generic

+
+ API Keys + Bearer Tokens + Basic Auth + Passwords in URLs + High Entropy +
+
+
+
+ + +
+ +

10 scanning vectors per page

+

Every page load triggers a comprehensive scan across all the places where secrets commonly leak.

+
+
+
1
+
+

Script src URLs

+

Examines URLs in script tags for embedded API keys and tokens passed as query parameters.

+
+
+
+
2
+
+

Inline Scripts

+

Parses all inline JavaScript blocks on the page for hardcoded credentials and secret assignments.

+
+
+
+
3
+
+

External Scripts

+

Fetches and analyzes external JavaScript files loaded by the page for leaked keys and tokens.

+
+
+
+
4
+
+

Meta Tags

+

Inspects meta tag content attributes where configuration keys and tokens are sometimes exposed.

+
+
+
+
5
+
+

Hidden Form Fields

+

Scans hidden input fields that developers use to pass tokens and API keys through forms.

+
+
+
+
6
+
+

Data Attributes

+

Checks HTML data-* attributes where frontend frameworks often store configuration secrets.

+
+
+
+
7
+
+

HTML Comments

+

Extracts and scans HTML comments for accidentally committed credentials and debug tokens.

+
+
+
+
8
+
+

URL Parameters

+

Analyzes query strings and URL fragments for API keys and authentication tokens passed in the clear.

+
+
+
+
9
+
+

Web Storage

+

Monitors localStorage and sessionStorage for secrets stored client-side by web applications.

+
+
+
+
10
+
+

Network Responses

+

Intercepts XHR and Fetch responses to detect secrets returned by APIs and backend services.

+
+
+
+
+ + +
+ +

Up and running in under a minute

+

Two ways to install. Both take less than 60 seconds. No build tools required.

+
+
+
+ From Release + Recommended +
+
+
+
1
+
Download the latest release from GitHub Releases
+
+
+
2
+
Extract the ZIP file to a folder on your machine
+
+
+
3
+
Open Chrome and navigate to chrome://extensions
+
+
+
4
+
Enable Developer mode in the top-right corner
+
+
+
5
+
Click Load unpacked and select the extracted folder
+
+
+
+
+
+ From Source +
+
+
+
1
+
Clone the repository:
git clone https://github.com/momenbasel/keyFinder.git
+
+
+
2
+
Open Chrome and navigate to chrome://extensions
+
+
+
3
+
Enable Developer mode in the top-right corner
+
+
+
4
+
Click Load unpacked and select the cloned keyFinder directory
+
+
+
+
+
+ + +
+

Start finding leaked secrets today

+

Install keyFinder and let it passively scan every page you visit. No configuration needed.

+ +
+ + + + + + + + diff --git a/js/background.js b/js/background.js deleted file mode 100644 index bf2592d..0000000 --- a/js/background.js +++ /dev/null @@ -1,103 +0,0 @@ -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; -} - -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: "" }); -} diff --git a/js/content.js b/js/content.js deleted file mode 100644 index 0af1ca9..0000000 --- a/js/content.js +++ /dev/null @@ -1,333 +0,0 @@ -(async function () { - "use strict"; - - 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; - } - - function isHighEntropy(str) { - if (str.length < 12) return false; - return shannonEntropy(str) > 3.5; - } - - 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; - - 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 {} - } - } - - 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"); - } - } - - 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", - }); - } - }); - - 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}`); - } -})(); diff --git a/js/interceptor.js b/js/interceptor.js deleted file mode 100644 index c3e9160..0000000 --- a/js/interceptor.js +++ /dev/null @@ -1,106 +0,0 @@ -(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; - }; -})(); diff --git a/js/patterns.js b/js/patterns.js deleted file mode 100644 index 2afcc7f..0000000 --- a/js/patterns.js +++ /dev/null @@ -1,134 +0,0 @@ -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" }, -]; diff --git a/js/popup.js b/js/popup.js deleted file mode 100644 index 0665b37..0000000 --- a/js/popup.js +++ /dev/null @@ -1,82 +0,0 @@ -document.addEventListener("DOMContentLoaded", init); - -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 = ""; - - document.getElementById("keywordCount").textContent = keywords.length; - - if (keywords.length === 0) { - list.innerHTML = '
  • No keywords configured
  • '; - 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; -} - -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); -} diff --git a/js/results.js b/js/results.js deleted file mode 100644 index 0e10cdc..0000000 --- a/js/results.js +++ /dev/null @@ -1,249 +0,0 @@ -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(); -} diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 19585d7..0000000 --- a/manifest.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "KeyFinder", - "description": "Passively discovers API keys, tokens, and secrets leaked in page scripts, DOM, network responses, and browser storage.", - "version": "2.0.0", - "manifest_version": 3, - "action": { - "default_icon": { - "16": "icons/icon16.png", - "48": "icons/icon48.png", - "128": "icons/icon128.png" - }, - "default_popup": "popup.html" - }, - "icons": { - "16": "icons/icon16.png", - "48": "icons/icon48.png", - "128": "icons/icon128.png" - }, - "content_scripts": [ - { - "matches": [""], - "js": ["js/patterns.js", "js/content.js"], - "run_at": "document_idle", - "all_frames": true - }, - { - "matches": [""], - "js": ["js/interceptor.js"], - "run_at": "document_start", - "world": "MAIN", - "all_frames": true - } - ], - "background": { - "service_worker": "js/background.js" - }, - "permissions": ["activeTab", "storage"] -} diff --git a/popup.html b/popup.html deleted file mode 100644 index dcd8d2a..0000000 --- a/popup.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - KeyFinder - - - -
    -
    - KeyFinder -

    KeyFinder

    - v2.0 -
    -

    Passive API key & secret discovery

    -
    - -
    -
    - - - Findings -
    -
    - - - Keywords -
    -
    - -
    -

    Keywords

    -
    - - -
    - -
      -
      - - - - - - diff --git a/results.html b/results.html deleted file mode 100644 index 86a309b..0000000 --- a/results.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - KeyFinder - Findings - - - -
      -
      - KeyFinder -

      KeyFinder v2.0

      -
      -
      -
      - - - - -
      -
      - - - -
      -
      -
      - -
      - -
      - - - - - - - - - - - - - - - - -
      #SeverityProviderPatternMatchTypeDomainSourceTime
      - -
      - - - -