Add GitHub Pages landing page

This commit is contained in:
moamen
2026-04-10 07:08:09 +02:00
parent f25d07f97d
commit 7b5aad4864
22 changed files with 981 additions and 1975 deletions
-10
View File
@@ -1,10 +0,0 @@
.DS_Store
*.crx
*.pem
*.zip
.idea/
.vscode/
*.swp
*.swo
*~
.claude/
-21
View File
@@ -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.
-61
View File
@@ -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/)
-135
View File
@@ -1,135 +0,0 @@
<p align="center">
<img width="128" height="128" alt="KeyFinder logo" src="https://raw.githubusercontent.com/momenbasel/keyFinder/master/icons/icon128.png">
</p>
<h1 align="center">KeyFinder</h1>
<p align="center">
<strong>Passive API key and secret discovery for Chrome</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/manifest-v3-blue"/>
<img src="https://img.shields.io/badge/Chrome-Extension-green"/>
<img src="https://img.shields.io/github/license/momenbasel/keyFinder"/>
<img src="https://img.shields.io/github/v/release/momenbasel/keyFinder"/>
<img src="https://img.shields.io/github/downloads/momenbasel/keyFinder/total.svg"/>
</p>
<hr>
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 `<script>` tag contents for secret patterns
3. **External scripts** - Fetches and scans same-origin JavaScript files
4. **Meta tags** - Checks `<meta>` tags for leaked API keys and tokens
5. **Hidden form fields** - Inspects `<input type="hidden">` values
6. **Data attributes** - Scans `data-*` attributes for sensitive values
7. **HTML comments** - Parses comment nodes for accidentally committed secrets
8. **URL parameters** - Analyzes links and hrefs for tokens in query strings
9. **Web storage** - Scans localStorage and sessionStorage
10. **Network responses** - Intercepts XHR and Fetch responses for leaked secrets
Additionally, **Shannon entropy analysis** is applied to detect random high-entropy strings that may be undocumented secret formats.
## Features
- **Zero dependencies** - Pure vanilla JavaScript, no jQuery, no external libraries
- **Manifest V3** - Built for modern Chrome with service worker architecture
- **Passive scanning** - Runs automatically on every page load
- **Custom keywords** - Add your own search terms to scan for
- **Dashboard** - Professional results page with filtering, sorting, and search
- **Export** - Download findings as JSON or CSV
- **Badge counter** - Shows finding count on the extension icon
- **Low footprint** - Minimal CPU and memory usage
- **All frames** - Scans iframes and embedded content
## Installation
### From Release (Recommended)
1. Go to [Releases](https://github.com/momenbasel/keyFinder/releases) and download the latest `.crx` file
2. Open Chrome and navigate to `chrome://extensions`
3. Enable **Developer mode** (top right toggle)
4. Drag and drop the `.crx` file onto the page
### From Source
```bash
git clone https://github.com/momenbasel/keyFinder.git
```
1. Open Chrome and go to `chrome://extensions`
2. Enable **Developer mode**
3. Click **Load unpacked** and select the `keyFinder` folder
## Usage
1. **Install** the extension
2. **Browse** the web normally - KeyFinder scans every page in the background
3. Click the **extension icon** to see stats and manage keywords
4. Click **View Findings** to open the full results dashboard
5. **Filter** by severity, provider, or type
6. **Export** findings as JSON or CSV for reporting
## Adding Custom Keywords
Click the extension icon, type a keyword in the input field, and click **Add**. The keyword will be used to scan script URLs, inline code, and key-value assignments on every page you visit.
Default keywords: `key`, `api_key`, `apikey`, `api-key`, `secret`, `token`, `access_token`, `auth`, `credential`, `password`, `client_id`, `client_secret`
## Architecture
```
keyFinder/
manifest.json # MV3 manifest
popup.html # Extension popup UI
results.html # Findings dashboard
js/
background.js # Service worker - storage and message handling
patterns.js # 80+ secret detection regex patterns
content.js # Page scanner - DOM, scripts, network interception
popup.js # Popup logic
results.js # Dashboard logic with filtering and export
css/
popup.css # Popup styles
results.css # Dashboard styles
icons/
icon16.png
icon48.png
icon128.png
```
## Disclaimer
This tool is intended for **security research and authorized testing only**. Use it to identify leaked secrets on your own applications or during authorized penetration tests. You are responsible for your own actions.
## License
MIT
## Author
[@momenbassel](https://x.com/momenbassel) - [LinkedIn](https://www.linkedin.com/in/momenbasel/)
-240
View File
@@ -1,240 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 360px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #0f0f0f;
color: #e0e0e0;
font-size: 13px;
line-height: 1.5;
}
.header {
padding: 16px 16px 12px;
border-bottom: 1px solid #1e1e1e;
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 100%);
}
.header-brand {
display: flex;
align-items: center;
gap: 8px;
}
.header-icon {
width: 28px;
height: 28px;
}
.header-brand h1 {
font-size: 18px;
font-weight: 700;
color: #ffffff;
letter-spacing: -0.3px;
}
.version {
font-size: 10px;
font-weight: 500;
color: #666;
background: #1a1a1a;
padding: 2px 6px;
border-radius: 4px;
margin-left: 4px;
}
.header-tagline {
margin-top: 4px;
font-size: 11px;
color: #666;
}
.stats {
display: flex;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid #1e1e1e;
}
.stat-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background: #141414;
border-radius: 8px;
border: 1px solid #1e1e1e;
}
.stat-number {
font-size: 22px;
font-weight: 700;
color: #fff;
}
.stat-label {
font-size: 10px;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 2px;
}
.section {
padding: 12px 16px;
}
.section-title {
font-size: 11px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 8px;
}
.keyword-form {
display: flex;
gap: 6px;
margin-bottom: 8px;
}
.keyword-form input {
flex: 1;
padding: 7px 10px;
background: #141414;
border: 1px solid #2a2a2a;
border-radius: 6px;
color: #e0e0e0;
font-size: 12px;
outline: none;
transition: border-color 0.15s;
}
.keyword-form input:focus {
border-color: #4a9eff;
}
.keyword-form input::placeholder {
color: #444;
}
.keyword-form button {
padding: 7px 14px;
background: #4a9eff;
color: #fff;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.keyword-form button:hover {
background: #3a8eef;
}
.error-msg {
padding: 6px 10px;
background: #2a1515;
border: 1px solid #4a2020;
border-radius: 6px;
color: #ff6b6b;
font-size: 11px;
margin-bottom: 8px;
}
.keyword-list {
list-style: none;
max-height: 240px;
overflow-y: auto;
}
.keyword-list::-webkit-scrollbar {
width: 4px;
}
.keyword-list::-webkit-scrollbar-track {
background: transparent;
}
.keyword-list::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
.keyword-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
border-radius: 6px;
transition: background 0.1s;
}
.keyword-item:hover {
background: #1a1a1a;
}
.keyword-label {
font-family: "SF Mono", "Fira Code", "Consolas", monospace;
font-size: 12px;
color: #ccc;
}
.keyword-remove {
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: #555;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
transition: all 0.1s;
}
.keyword-remove:hover {
background: #2a1515;
color: #ff6b6b;
}
.empty-state {
text-align: center;
padding: 16px;
color: #444;
font-size: 12px;
}
.footer {
padding: 12px 16px;
border-top: 1px solid #1e1e1e;
}
.results-btn {
display: block;
text-align: center;
padding: 10px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #4a9eff;
text-decoration: none;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
border: 1px solid #1e2d4a;
transition: all 0.15s;
}
.results-btn:hover {
background: linear-gradient(135deg, #1e2040 0%, #1a2848 100%);
border-color: #2a4070;
}
-341
View File
@@ -1,341 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #0a0a0a;
color: #e0e0e0;
font-size: 13px;
line-height: 1.5;
min-height: 100vh;
}
.header {
padding: 16px 24px;
background: #0f0f0f;
border-bottom: 1px solid #1e1e1e;
display: flex;
flex-direction: column;
gap: 12px;
}
.header-left {
display: flex;
align-items: center;
gap: 10px;
}
.header-icon {
width: 32px;
height: 32px;
}
.header-left h1 {
font-size: 20px;
font-weight: 700;
color: #fff;
}
.version {
font-size: 11px;
color: #555;
font-weight: 400;
}
.header-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
justify-content: space-between;
}
.filter-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-group select,
.filter-group input {
padding: 6px 10px;
background: #141414;
border: 1px solid #2a2a2a;
border-radius: 6px;
color: #ccc;
font-size: 12px;
outline: none;
}
.filter-group select:focus,
.filter-group input:focus {
border-color: #4a9eff;
}
.filter-group input {
min-width: 180px;
}
.btn-group {
display: flex;
gap: 6px;
}
.btn {
padding: 6px 14px;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.btn-secondary {
background: #1a1a1a;
color: #ccc;
border: 1px solid #2a2a2a;
}
.btn-secondary:hover {
background: #222;
border-color: #444;
}
.btn-danger {
background: #2a1515;
color: #ff6b6b;
border: 1px solid #4a2020;
}
.btn-danger:hover {
background: #3a1a1a;
}
.stats-bar {
display: flex;
gap: 1px;
padding: 12px 24px;
background: #0f0f0f;
border-bottom: 1px solid #1e1e1e;
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
background: #111;
border-radius: 6px;
margin: 0 4px;
}
.stat-num {
font-size: 20px;
font-weight: 700;
color: #fff;
}
.stat-lbl {
font-size: 10px;
color: #555;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-critical .stat-num { color: #ff4444; }
.stat-high .stat-num { color: #ff8c42; }
.stat-medium .stat-num { color: #ffd166; }
.stat-low .stat-num { color: #4ecdc4; }
.main {
padding: 16px 24px;
}
.findings-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.findings-table thead th {
padding: 8px 10px;
text-align: left;
font-size: 10px;
font-weight: 600;
color: #555;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #1e1e1e;
white-space: nowrap;
position: sticky;
top: 0;
background: #0a0a0a;
}
.findings-table tbody tr {
border-bottom: 1px solid #141414;
transition: background 0.1s;
}
.findings-table tbody tr:hover {
background: #111;
}
.findings-table td {
padding: 8px 10px;
vertical-align: middle;
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.3px;
}
.badge-critical {
background: #3a0a0a;
color: #ff4444;
border: 1px solid #5a1515;
}
.badge-high {
background: #3a1f0a;
color: #ff8c42;
border: 1px solid #5a3015;
}
.badge-medium {
background: #3a3a0a;
color: #ffd166;
border: 1px solid #5a5a15;
}
.badge-low {
background: #0a3a30;
color: #4ecdc4;
border: 1px solid #155a4a;
}
.type-badge {
display: inline-block;
padding: 2px 6px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 4px;
font-size: 10px;
color: #888;
font-family: "SF Mono", "Fira Code", monospace;
}
.match-value {
font-family: "SF Mono", "Fira Code", "Consolas", monospace;
font-size: 11px;
background: #141414;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #1e1e1e;
color: #4ecdc4;
max-width: 260px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.td-provider {
font-weight: 600;
color: #aaa;
}
.td-pattern {
color: #888;
font-size: 11px;
}
.td-domain {
color: #4a9eff;
}
.td-time {
color: #555;
font-size: 11px;
white-space: nowrap;
}
a {
color: #4a9eff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.btn-icon {
padding: 3px 8px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 4px;
color: #888;
font-size: 10px;
cursor: pointer;
margin-right: 4px;
transition: all 0.1s;
}
.btn-icon:hover {
background: #222;
color: #ccc;
}
.btn-icon-danger:hover {
background: #2a1515;
color: #ff6b6b;
border-color: #4a2020;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #444;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.3;
}
.empty-state p {
font-size: 14px;
max-width: 400px;
margin: 0 auto;
line-height: 1.6;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

+981
View File
@@ -0,0 +1,981 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>keyFinder - Find Leaked API Keys, Tokens & Secrets in Your Browser</title>
<meta name="description" content="Chrome extension that passively finds leaked API keys, tokens, and secrets on every page you visit. 80+ detection patterns. 10 attack surfaces. Zero config. Manifest V3.">
<meta name="keywords" content="chrome extension secret scanner, api key finder, leaked credentials detector, secret detection, api keys, api security, browser extension, bug bounty, pentesting, infosec, javascript, manifest v3, osint, reconnaissance, secrets, aws keys, cybersecurity, hacking, leaked credentials, secret scanner, security tool">
<meta name="author" content="Moamen Basel">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://momenbasel.github.io/keyFinder/">
<!-- Open Graph -->
<meta property="og:title" content="keyFinder - Passively Find Leaked Secrets in Your Browser">
<meta property="og:description" content="Chrome extension that scans every page for leaked API keys, tokens, and credentials. 80+ patterns. 10 attack surfaces. Zero config.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://momenbasel.github.io/keyFinder/">
<meta property="og:image" content="https://raw.githubusercontent.com/momenbasel/keyFinder/master/icons/social-preview.png">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@momenbassel">
<meta name="twitter:creator" content="@momenbassel">
<meta name="twitter:title" content="keyFinder - Passively Find Leaked Secrets in Your Browser">
<meta name="twitter:description" content="Chrome extension scanning every page for leaked API keys, tokens, and credentials. 80+ patterns. 10 attack surfaces. Zero config.">
<meta name="twitter:image" content="https://raw.githubusercontent.com/momenbasel/keyFinder/master/icons/social-preview.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="https://raw.githubusercontent.com/momenbasel/keyFinder/master/icons/icon128.png">
<!-- JSON-LD Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "keyFinder",
"applicationCategory": "BrowserExtension",
"operatingSystem": "Chrome, Chromium",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"description": "Chrome extension that passively finds leaked API keys, tokens, and secrets on every page you visit. 80+ detection patterns across 10 attack surfaces with zero configuration.",
"url": "https://momenbasel.github.io/keyFinder/",
"downloadUrl": "https://github.com/momenbasel/keyFinder/releases",
"author": { "@type": "Person", "name": "Moamen Basel", "url": "https://github.com/momenbasel" },
"license": "https://opensource.org/licenses/MIT",
"programmingLanguage": "JavaScript",
"browserRequirements": "Requires Chrome or Chromium-based browser with Manifest V3 support"
}
</script>
<style>
:root {
--bg: #0a0e17;
--bg-card: #111827;
--bg-code: #0d1520;
--border: #1e293b;
--text: #e2e8f0;
--text-dim: #94a3b8;
--text-muted: #64748b;
--accent: #10b981;
--accent-secondary: #22d3ee;
--accent-glow: rgba(16, 185, 129, 0.15);
--accent-glow-secondary: rgba(34, 211, 238, 0.1);
--green: #34d399;
--yellow: #fbbf24;
--purple: #a78bfa;
--pink: #f472b6;
--red: #f87171;
--cyan: #22d3ee;
--radius: 12px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
overflow-x: hidden;
}
a { color: var(--accent); text-decoration: none; transition: opacity 0.2s; }
a:hover { opacity: 0.85; }
code, pre {
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace;
}
/* ---- NAV ---- */
nav {
position: fixed; top: 0; width: 100%; z-index: 100;
background: rgba(10, 14, 23, 0.8);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 0 2rem;
}
.nav-inner {
max-width: 1200px; margin: 0 auto;
display: flex; align-items: center; justify-content: space-between;
height: 64px;
}
.nav-logo {
display: flex; align-items: center; gap: 10px;
font-weight: 700; font-size: 1.1rem; color: var(--text);
}
.nav-logo img { height: 28px; width: 28px; border-radius: 6px; }
.nav-links { display: flex; gap: 2rem; align-items: center; }
.nav-links a { color: var(--text-dim); font-size: 0.9rem; font-weight: 500; }
.nav-links a:hover { color: var(--text); }
.nav-links .btn-sm {
background: var(--accent);
color: #0a0e17;
padding: 6px 16px;
border-radius: 8px;
font-weight: 600;
font-size: 0.85rem;
}
.nav-links .btn-sm:hover { opacity: 0.9; }
/* ---- HERO ---- */
.hero {
min-height: 100vh;
display: flex; align-items: center; justify-content: center;
padding: 120px 2rem 80px;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -40%; left: 50%; transform: translateX(-50%);
width: 800px; height: 800px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
pointer-events: none;
}
.hero-content {
max-width: 800px;
text-align: center;
position: relative; z-index: 1;
}
.hero-badge {
display: inline-flex; align-items: center; gap: 8px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 100px;
padding: 6px 16px;
font-size: 0.82rem;
color: var(--text-dim);
margin-bottom: 2rem;
}
.hero-badge span { color: var(--accent); font-weight: 600; }
.hero h1 {
font-size: clamp(2.5rem, 6vw, 4rem);
font-weight: 800;
line-height: 1.15;
letter-spacing: -0.03em;
margin-bottom: 1.25rem;
}
.hero h1 .gradient {
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
font-size: 1.2rem;
color: var(--text-dim);
max-width: 600px;
margin: 0 auto 2.5rem;
}
.hero-actions {
display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;
}
.btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 12px 28px;
border-radius: 10px;
font-weight: 600;
font-size: 0.95rem;
transition: all 0.2s;
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--accent);
color: #0a0e17;
}
.btn-primary:hover { box-shadow: 0 0 30px var(--accent-glow); transform: translateY(-1px); color: #0a0e17; }
.btn-secondary {
background: var(--bg-card);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover { border-color: var(--text-muted); transform: translateY(-1px); }
/* ---- SURFACES VISUAL ---- */
.surfaces-visual {
margin-top: 4rem;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
max-width: 640px;
margin-left: auto;
margin-right: auto;
}
.surface-chip {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 8px;
text-align: center;
transition: border-color 0.3s, transform 0.2s;
}
.surface-chip:hover { border-color: var(--accent); transform: translateY(-2px); }
.surface-chip .s-icon {
font-size: 1.4rem;
margin-bottom: 6px;
display: block;
color: var(--accent);
}
.surface-chip .s-icon svg { width: 24px; height: 24px; display: inline-block; }
.surface-chip .s-label {
font-size: 0.7rem;
color: var(--text-dim);
line-height: 1.3;
font-weight: 500;
}
/* ---- SECTIONS ---- */
section {
padding: 100px 2rem;
max-width: 1200px;
margin: 0 auto;
}
.section-label {
text-transform: uppercase;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--accent);
margin-bottom: 0.75rem;
}
.section-title {
font-size: clamp(1.8rem, 4vw, 2.5rem);
font-weight: 800;
letter-spacing: -0.02em;
margin-bottom: 1rem;
}
.section-desc {
color: var(--text-dim);
font-size: 1.05rem;
max-width: 600px;
margin-bottom: 3rem;
}
/* ---- FEATURES GRID ---- */
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1.25rem;
}
.feature-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px;
transition: border-color 0.3s, transform 0.2s;
}
.feature-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.feature-icon {
width: 44px; height: 44px;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 1.3rem;
margin-bottom: 16px;
}
.feature-icon.emerald { background: rgba(16, 185, 129, 0.12); color: var(--accent); }
.feature-icon.cyan { background: rgba(34, 211, 238, 0.12); color: var(--cyan); }
.feature-icon.purple { background: rgba(167, 139, 250, 0.12); color: var(--purple); }
.feature-icon.yellow { background: rgba(251, 191, 36, 0.12); color: var(--yellow); }
.feature-icon.pink { background: rgba(244, 114, 182, 0.12); color: var(--pink); }
.feature-icon.red { background: rgba(248, 113, 113, 0.12); color: var(--red); }
.feature-card h3 {
font-size: 1.05rem; font-weight: 700;
margin-bottom: 8px;
}
.feature-card p {
color: var(--text-dim); font-size: 0.9rem; line-height: 1.6;
}
/* ---- DETECTION COVERAGE ---- */
.detection-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.25rem;
}
.detection-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px;
transition: border-color 0.3s;
}
.detection-card:hover { border-color: var(--accent); }
.detection-card h4 {
font-size: 0.95rem;
font-weight: 700;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.detection-card h4 .d-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.detection-card .providers-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.detection-card .prov-tag {
display: inline-block;
background: rgba(16, 185, 129, 0.08);
border: 1px solid rgba(16, 185, 129, 0.15);
color: var(--text-dim);
padding: 3px 10px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
}
/* ---- ATTACK SURFACES ---- */
.surfaces-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 1rem;
}
.surface-item {
display: flex;
align-items: flex-start;
gap: 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 24px;
transition: border-color 0.3s;
}
.surface-item:hover { border-color: var(--accent); }
.surface-num {
flex-shrink: 0;
width: 32px; height: 32px;
border-radius: 8px;
background: rgba(16, 185, 129, 0.12);
color: var(--accent);
display: flex; align-items: center; justify-content: center;
font-weight: 700;
font-size: 0.85rem;
}
.surface-item h4 {
font-size: 0.95rem;
font-weight: 700;
margin-bottom: 4px;
}
.surface-item p {
color: var(--text-dim);
font-size: 0.85rem;
line-height: 1.5;
}
/* ---- INSTALL SECTION ---- */
.install-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 1.5rem;
}
.install-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.install-header {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
font-weight: 700;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
}
.install-header .tag {
display: inline-block;
background: rgba(16, 185, 129, 0.1);
color: var(--accent);
padding: 2px 8px;
border-radius: 6px;
font-size: 0.72rem;
font-weight: 600;
}
.install-body {
padding: 20px 24px;
}
.install-step {
display: flex;
gap: 12px;
padding: 8px 0;
align-items: flex-start;
}
.install-step .step-num {
flex-shrink: 0;
width: 24px; height: 24px;
border-radius: 6px;
background: rgba(16, 185, 129, 0.1);
color: var(--accent);
display: flex; align-items: center; justify-content: center;
font-weight: 700;
font-size: 0.72rem;
}
.install-step .step-text {
color: var(--text-dim);
font-size: 0.88rem;
line-height: 1.6;
}
.install-step .step-text code {
background: var(--bg-code);
border: 1px solid var(--border);
padding: 1px 6px;
border-radius: 4px;
font-size: 0.82rem;
color: var(--text);
}
/* ---- STATS ---- */
.stats-row {
display: flex; gap: 2rem; justify-content: center; flex-wrap: wrap;
margin-top: 4rem;
}
.stat {
text-align: center;
min-width: 140px;
}
.stat-val {
font-size: 2.2rem;
font-weight: 800;
background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 4px;
}
/* ---- CTA ---- */
.cta {
text-align: center;
padding: 100px 2rem;
position: relative;
}
.cta::before {
content: '';
position: absolute;
bottom: 0; left: 50%; transform: translateX(-50%);
width: 600px; height: 400px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
pointer-events: none;
}
.cta h2 {
font-size: clamp(1.8rem, 4vw, 2.5rem);
font-weight: 800;
margin-bottom: 1rem;
}
.cta p {
color: var(--text-dim);
font-size: 1.05rem;
max-width: 500px;
margin: 0 auto 2rem;
}
/* ---- FOOTER ---- */
footer {
border-top: 1px solid var(--border);
padding: 40px 2rem;
text-align: center;
color: var(--text-muted);
font-size: 0.85rem;
}
footer a { color: var(--text-dim); }
.footer-links {
display: flex; gap: 2rem; justify-content: center;
margin-bottom: 1rem;
}
/* ---- RESPONSIVE ---- */
@media (max-width: 768px) {
.nav-links a:not(.btn-sm) { display: none; }
.features-grid { grid-template-columns: 1fr; }
.detection-grid { grid-template-columns: 1fr; }
.surfaces-list { grid-template-columns: 1fr; }
.install-grid { grid-template-columns: 1fr; }
.surfaces-visual { grid-template-columns: repeat(3, 1fr); }
section { padding: 60px 1.25rem; }
}
@media (max-width: 480px) {
.surfaces-visual { grid-template-columns: repeat(2, 1fr); }
}
/* ---- ANIMATIONS ---- */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate {
opacity: 0;
animation: fadeUp 0.6s ease forwards;
}
.animate.d1 { animation-delay: 0.1s; }
.animate.d2 { animation-delay: 0.2s; }
.animate.d3 { animation-delay: 0.3s; }
.animate.d4 { animation-delay: 0.4s; }
</style>
</head>
<body>
<!-- NAV -->
<nav>
<div class="nav-inner">
<a href="#" class="nav-logo">
<img src="https://raw.githubusercontent.com/momenbasel/keyFinder/master/icons/icon128.png" alt="keyFinder">
keyFinder
</a>
<div class="nav-links">
<a href="#features">Features</a>
<a href="#detection">Detection</a>
<a href="#surfaces">Surfaces</a>
<a href="#install">Install</a>
<a href="https://github.com/momenbasel/keyFinder" class="btn-sm">GitHub</a>
</div>
</div>
</nav>
<!-- HERO -->
<section class="hero">
<div class="hero-content">
<div class="hero-badge animate">
<span>558+ stars</span> Trusted by security researchers
</div>
<h1 class="animate d1">
Find leaked secrets,<br>
<span class="gradient">passively.</span>
</h1>
<p class="animate d2">
A Chrome extension that scans every page you visit for API keys, tokens, and credentials.
80+ patterns. 10 attack surfaces. Zero config.
</p>
<div class="hero-actions animate d3">
<a href="https://github.com/momenbasel/keyFinder/releases" class="btn btn-primary">
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 0a8 8 0 100 16A8 8 0 008 0zm1 11.5v-4h2L8 4 5 7.5h2v4h2z"/></svg>
Install from Releases
</a>
<a href="https://github.com/momenbasel/keyFinder" class="btn btn-secondary">
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
View Source
</a>
</div>
<!-- 10 Attack Surfaces Visual -->
<div class="surfaces-visual animate d4">
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg></span>
<span class="s-label">Script URLs</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg></span>
<span class="s-label">Inline Scripts</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 2H3v16h5v4l4-4h5l4-4V2z"/></svg></span>
<span class="s-label">External JS</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg></span>
<span class="s-label">Meta Tags</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg></span>
<span class="s-label">Hidden Fields</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg></span>
<span class="s-label">Data Attrs</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg></span>
<span class="s-label">Comments</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg></span>
<span class="s-label">URL Params</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg></span>
<span class="s-label">Web Storage</span>
</div>
<div class="surface-chip">
<span class="s-icon"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></span>
<span class="s-label">Network</span>
</div>
</div>
</div>
</section>
<!-- FEATURES -->
<section id="features">
<div class="section-label">Features</div>
<h2 class="section-title">Powerful detection, zero friction</h2>
<p class="section-desc">Install once. Every page you visit is automatically scanned for leaked secrets across all attack surfaces.</p>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon emerald">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><path d="M9 14l2 2 4-4"/></svg>
</div>
<h3>80+ Detection Patterns</h3>
<p>Covers cloud providers, payment platforms, communication tools, AI services, databases, SaaS, infrastructure keys, crypto secrets, and generic patterns.</p>
</div>
<div class="feature-card">
<div class="feature-icon cyan">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
</div>
<h3>10 Attack Surfaces</h3>
<p>Scans script URLs, inline scripts, external JS, meta tags, hidden form fields, data attributes, HTML comments, URL parameters, web storage, and network responses.</p>
</div>
<div class="feature-card">
<div class="feature-icon purple">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<h3>Shannon Entropy Analysis</h3>
<p>Calculates Shannon entropy for detected strings. High-entropy values get flagged as likely secrets, reducing false positives on random-looking tokens.</p>
</div>
<div class="feature-card">
<div class="feature-icon yellow">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
</div>
<h3>Zero Dependencies</h3>
<p>Pure vanilla JavaScript. No external libraries, no build step, no framework. Lightweight, fast, and auditable. Just the extension code and nothing else.</p>
</div>
<div class="feature-card">
<div class="feature-icon pink">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
</div>
<h3>Manifest V3</h3>
<p>Built on the latest Chrome extension architecture with a service worker. Future-proof, secure, and compatible with all modern Chromium browsers.</p>
</div>
<div class="feature-card">
<div class="feature-icon red">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</div>
<h3>Export and Report</h3>
<p>Professional dashboard with filtering, sorting, and search. Export findings as JSON or CSV. Badge counter on the extension icon shows live results.</p>
</div>
</div>
<div class="stats-row">
<div class="stat">
<div class="stat-val">80+</div>
<div class="stat-label">Detection Patterns</div>
</div>
<div class="stat">
<div class="stat-val">10</div>
<div class="stat-label">Attack Surfaces</div>
</div>
<div class="stat">
<div class="stat-val">558+</div>
<div class="stat-label">GitHub Stars</div>
</div>
<div class="stat">
<div class="stat-val">0</div>
<div class="stat-label">Dependencies</div>
</div>
</div>
</section>
<!-- DETECTION COVERAGE -->
<section id="detection">
<div class="section-label">Detection Coverage</div>
<h2 class="section-title">Secrets across every category</h2>
<p class="section-desc">From cloud provider keys to cryptocurrency wallet seeds, keyFinder recognizes credentials across the entire modern stack.</p>
<div class="detection-grid">
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--accent)"></span>Cloud</h4>
<div class="providers-list">
<span class="prov-tag">AWS</span>
<span class="prov-tag">GCP</span>
<span class="prov-tag">Azure</span>
<span class="prov-tag">DigitalOcean</span>
<span class="prov-tag">Heroku</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--cyan)"></span>Source Control</h4>
<div class="providers-list">
<span class="prov-tag">GitHub</span>
<span class="prov-tag">GitLab</span>
<span class="prov-tag">Bitbucket</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--yellow)"></span>Payments</h4>
<div class="providers-list">
<span class="prov-tag">Stripe</span>
<span class="prov-tag">PayPal</span>
<span class="prov-tag">Square</span>
<span class="prov-tag">Braintree</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--purple)"></span>Communication</h4>
<div class="providers-list">
<span class="prov-tag">Slack</span>
<span class="prov-tag">Discord</span>
<span class="prov-tag">Telegram</span>
<span class="prov-tag">Twilio</span>
<span class="prov-tag">SendGrid</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--pink)"></span>AI / ML</h4>
<div class="providers-list">
<span class="prov-tag">OpenAI</span>
<span class="prov-tag">Anthropic</span>
<span class="prov-tag">Cohere</span>
<span class="prov-tag">HuggingFace</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--red)"></span>Databases</h4>
<div class="providers-list">
<span class="prov-tag">MongoDB</span>
<span class="prov-tag">PostgreSQL</span>
<span class="prov-tag">Redis</span>
<span class="prov-tag">MySQL</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--green)"></span>SaaS</h4>
<div class="providers-list">
<span class="prov-tag">Mailchimp</span>
<span class="prov-tag">Algolia</span>
<span class="prov-tag">Firebase</span>
<span class="prov-tag">Shopify</span>
<span class="prov-tag">Zendesk</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: #f59e0b"></span>Infrastructure</h4>
<div class="providers-list">
<span class="prov-tag">Docker</span>
<span class="prov-tag">Kubernetes</span>
<span class="prov-tag">Terraform</span>
<span class="prov-tag">Vault</span>
<span class="prov-tag">NPM</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: #fb923c"></span>Crypto</h4>
<div class="providers-list">
<span class="prov-tag">Private Keys</span>
<span class="prov-tag">Seed Phrases</span>
<span class="prov-tag">Wallet Keys</span>
<span class="prov-tag">JWT Secrets</span>
</div>
</div>
<div class="detection-card">
<h4><span class="d-dot" style="background: var(--text-muted)"></span>Generic</h4>
<div class="providers-list">
<span class="prov-tag">API Keys</span>
<span class="prov-tag">Bearer Tokens</span>
<span class="prov-tag">Basic Auth</span>
<span class="prov-tag">Passwords in URLs</span>
<span class="prov-tag">High Entropy</span>
</div>
</div>
</div>
</section>
<!-- ATTACK SURFACES -->
<section id="surfaces">
<div class="section-label">Attack Surfaces</div>
<h2 class="section-title">10 scanning vectors per page</h2>
<p class="section-desc">Every page load triggers a comprehensive scan across all the places where secrets commonly leak.</p>
<div class="surfaces-list">
<div class="surface-item">
<div class="surface-num">1</div>
<div>
<h4>Script src URLs</h4>
<p>Examines URLs in script tags for embedded API keys and tokens passed as query parameters.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">2</div>
<div>
<h4>Inline Scripts</h4>
<p>Parses all inline JavaScript blocks on the page for hardcoded credentials and secret assignments.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">3</div>
<div>
<h4>External Scripts</h4>
<p>Fetches and analyzes external JavaScript files loaded by the page for leaked keys and tokens.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">4</div>
<div>
<h4>Meta Tags</h4>
<p>Inspects meta tag content attributes where configuration keys and tokens are sometimes exposed.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">5</div>
<div>
<h4>Hidden Form Fields</h4>
<p>Scans hidden input fields that developers use to pass tokens and API keys through forms.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">6</div>
<div>
<h4>Data Attributes</h4>
<p>Checks HTML data-* attributes where frontend frameworks often store configuration secrets.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">7</div>
<div>
<h4>HTML Comments</h4>
<p>Extracts and scans HTML comments for accidentally committed credentials and debug tokens.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">8</div>
<div>
<h4>URL Parameters</h4>
<p>Analyzes query strings and URL fragments for API keys and authentication tokens passed in the clear.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">9</div>
<div>
<h4>Web Storage</h4>
<p>Monitors localStorage and sessionStorage for secrets stored client-side by web applications.</p>
</div>
</div>
<div class="surface-item">
<div class="surface-num">10</div>
<div>
<h4>Network Responses</h4>
<p>Intercepts XHR and Fetch responses to detect secrets returned by APIs and backend services.</p>
</div>
</div>
</div>
</section>
<!-- INSTALLATION -->
<section id="install">
<div class="section-label">Installation</div>
<h2 class="section-title">Up and running in under a minute</h2>
<p class="section-desc">Two ways to install. Both take less than 60 seconds. No build tools required.</p>
<div class="install-grid">
<div class="install-card">
<div class="install-header">
From Release
<span class="tag">Recommended</span>
</div>
<div class="install-body">
<div class="install-step">
<div class="step-num">1</div>
<div class="step-text">Download the latest release from <a href="https://github.com/momenbasel/keyFinder/releases">GitHub Releases</a></div>
</div>
<div class="install-step">
<div class="step-num">2</div>
<div class="step-text">Extract the ZIP file to a folder on your machine</div>
</div>
<div class="install-step">
<div class="step-num">3</div>
<div class="step-text">Open Chrome and navigate to <code>chrome://extensions</code></div>
</div>
<div class="install-step">
<div class="step-num">4</div>
<div class="step-text">Enable <strong>Developer mode</strong> in the top-right corner</div>
</div>
<div class="install-step">
<div class="step-num">5</div>
<div class="step-text">Click <strong>Load unpacked</strong> and select the extracted folder</div>
</div>
</div>
</div>
<div class="install-card">
<div class="install-header">
From Source
</div>
<div class="install-body">
<div class="install-step">
<div class="step-num">1</div>
<div class="step-text">Clone the repository:<br><code>git clone https://github.com/momenbasel/keyFinder.git</code></div>
</div>
<div class="install-step">
<div class="step-num">2</div>
<div class="step-text">Open Chrome and navigate to <code>chrome://extensions</code></div>
</div>
<div class="install-step">
<div class="step-num">3</div>
<div class="step-text">Enable <strong>Developer mode</strong> in the top-right corner</div>
</div>
<div class="install-step">
<div class="step-num">4</div>
<div class="step-text">Click <strong>Load unpacked</strong> and select the cloned <code>keyFinder</code> directory</div>
</div>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section class="cta">
<h2>Start finding leaked secrets today</h2>
<p>Install keyFinder and let it passively scan every page you visit. No configuration needed.</p>
<div class="hero-actions">
<a href="https://github.com/momenbasel/keyFinder/releases" class="btn btn-primary">
Download Latest Release
</a>
<a href="https://github.com/momenbasel/keyFinder" class="btn btn-secondary">
View on GitHub
</a>
</div>
</section>
<!-- FOOTER -->
<footer>
<div class="footer-links">
<a href="https://github.com/momenbasel/keyFinder">GitHub</a>
<a href="https://github.com/momenbasel/keyFinder/releases">Releases</a>
<a href="https://github.com/momenbasel/keyFinder/issues">Issues</a>
<a href="https://github.com/momenbasel/keyFinder/blob/master/LICENSE">MIT License</a>
<a href="https://twitter.com/momenbassel">Twitter</a>
</div>
<p>Built by <a href="https://github.com/momenbasel">Moamen Basel</a></p>
</footer>
<!-- Intersection Observer for animations -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('animate');
observer.unobserve(e.target);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.feature-card, .detection-card, .surface-item, .install-card, .surface-chip').forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
observer.observe(el);
});
const style = document.createElement('style');
style.textContent = `.feature-card.animate, .detection-card.animate, .surface-item.animate, .install-card.animate, .surface-chip.animate { opacity: 1 !important; transform: translateY(0) !important; }`;
document.head.appendChild(style);
});
</script>
</body>
</html>
-103
View File
@@ -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: "" });
}
-333
View File
@@ -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}`);
}
})();
-106
View File
@@ -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;
};
})();
-134
View File
@@ -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" },
];
-82
View File
@@ -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 = '<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;
}
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);
}
-249
View File
@@ -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();
}
-38
View File
@@ -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": ["<all_urls>"],
"js": ["js/patterns.js", "js/content.js"],
"run_at": "document_idle",
"all_frames": true
},
{
"matches": ["<all_urls>"],
"js": ["js/interceptor.js"],
"run_at": "document_start",
"world": "MAIN",
"all_frames": true
}
],
"background": {
"service_worker": "js/background.js"
},
"permissions": ["activeTab", "storage"]
}
-54
View File
@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KeyFinder</title>
<link rel="stylesheet" href="css/popup.css">
</head>
<body>
<header class="header">
<div class="header-brand">
<img src="icons/icon48.png" alt="KeyFinder" class="header-icon">
<h1>KeyFinder</h1>
<span class="version">v2.0</span>
</div>
<p class="header-tagline">Passive API key & secret discovery</p>
</header>
<section class="stats" id="stats">
<div class="stat-card">
<span class="stat-number" id="findingCount">-</span>
<span class="stat-label">Findings</span>
</div>
<div class="stat-card">
<span class="stat-number" id="keywordCount">-</span>
<span class="stat-label">Keywords</span>
</div>
</section>
<section class="section">
<h2 class="section-title">Keywords</h2>
<form id="keywordForm" class="keyword-form">
<input
type="text"
id="keywordInput"
placeholder="Add a keyword (e.g. api_key)"
autocomplete="off"
spellcheck="false"
>
<button type="submit" id="addBtn">Add</button>
</form>
<div id="errorMsg" class="error-msg" hidden></div>
<ul id="keywordList" class="keyword-list"></ul>
</section>
<footer class="footer">
<a href="results.html" target="_blank" id="resultsLink" class="results-btn">
View Findings
</a>
</footer>
<script src="js/popup.js"></script>
</body>
</html>
-68
View File
@@ -1,68 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KeyFinder - Findings</title>
<link rel="stylesheet" href="css/results.css">
</head>
<body>
<header class="header">
<div class="header-left">
<img src="icons/icon48.png" alt="KeyFinder" class="header-icon">
<h1>KeyFinder <span class="version">v2.0</span></h1>
</div>
<div class="header-actions">
<div class="filter-group">
<select id="severityFilter">
<option value="all">All Severities</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
<select id="typeFilter">
<option value="all">All Types</option>
</select>
<select id="providerFilter">
<option value="all">All Providers</option>
</select>
<input type="text" id="searchBox" placeholder="Search findings...">
</div>
<div class="btn-group">
<button id="exportJsonBtn" class="btn btn-secondary">Export JSON</button>
<button id="exportCsvBtn" class="btn btn-secondary">Export CSV</button>
<button id="clearBtn" class="btn btn-danger">Clear All</button>
</div>
</div>
</header>
<div class="stats-bar" id="statsBar"></div>
<main class="main">
<table class="findings-table">
<thead>
<tr>
<th>#</th>
<th>Severity</th>
<th>Provider</th>
<th>Pattern</th>
<th>Match</th>
<th>Type</th>
<th>Domain</th>
<th>Source</th>
<th>Time</th>
<th></th>
</tr>
</thead>
<tbody id="findingsBody"></tbody>
</table>
<div id="emptyState" class="empty-state" hidden>
<div class="empty-icon">&#128269;</div>
<p>No findings yet. Browse some pages and KeyFinder will passively scan for leaked secrets.</p>
</div>
</main>
<script src="js/results.js"></script>
</body>
</html>