github-keyhunter / index.html
Fuckingbase's picture
جدی کار می‌کنه
9f7505b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitHub KeyHunter - Automated API Key Scanner</title>
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔑</text></svg>">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
body { font-family: 'Inter', sans-serif; }
.gradient-bg { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.glass-effect { backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.1); }
.scan-animation { animation: pulse 2s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.key-card { transition: all 0.3s ease; }
.key-card:hover { transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0,0,0,0.1); }
.status-active { background: linear-gradient(90deg, #10b981, #059669); }
.status-inactive { background: linear-gradient(90deg, #ef4444, #dc2626); }
.status-testing { background: linear-gradient(90deg, #f59e0b, #d97706); }
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<div id="vanta-bg" class="fixed inset-0 z-0"></div>
<nav class="relative z-10 glass-effect border-b border-gray-700">
<div class="container mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-feather="key" class="w-8 h-8 text-purple-400"></i>
<h1 class="text-2xl font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">GitHub KeyHunter</h1>
</div>
<div class="flex items-center space-x-4">
<button id="startScanBtn" class="bg-purple-600 hover:bg-purple-700 px-6 py-2 rounded-lg font-medium transition-all duration-300 flex items-center space-x-2">
<i data-feather="play" class="w-4 h-4"></i>
<span>Start Scan</span>
</button>
<button id="settingsBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300">
<i data-feather="settings" class="w-5 h-5"></i>
</button>
</div>
</div>
</div>
</nav>
<main class="relative z-10 container mx-auto px-6 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<div class="glass-effect rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Total Scanned</h3>
<i data-feather="search" class="w-6 h-6 text-blue-400"></i>
</div>
<div class="text-3xl font-bold text-blue-400" id="totalScanned">0</div>
<div class="text-sm text-gray-400 mt-2">Repositories</div>
</div>
<div class="glass-effect rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Keys Found</h3>
<i data-feather="key" class="w-6 h-6 text-yellow-400"></i>
</div>
<div class="text-3xl font-bold text-yellow-400" id="keysFound">0</div>
<div class="text-sm text-gray-400 mt-2">Potential API Keys</div>
</div>
<div class="glass-effect rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Valid Keys</h3>
<i data-feather="check-circle" class="w-6 h-6 text-green-400"></i>
</div>
<div class="text-3xl font-bold text-green-400" id="validKeys">0</div>
<div class="text-sm text-gray-400 mt-2">Working Keys</div>
</div>
</div>
<div class="glass-effect rounded-xl p-6 border border-gray-700 mb-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold">Scan Progress</h2>
<div class="flex items-center space-x-4">
<span id="scanStatus" class="px-3 py-1 rounded-full text-sm font-medium bg-gray-700">Idle</span>
<button id="pauseScanBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300 hidden">
<i data-feather="pause" class="w-4 h-4"></i>
</button>
</div>
</div>
<div class="mb-4">
<div class="flex justify-between text-sm mb-2">
<span>Progress</span>
<span id="progressPercent">0%</span>
</div>
<div class="w-full bg-gray-700 rounded-full h-3 overflow-hidden">
<div id="progressBar" class="bg-gradient-to-r from-purple-500 to-pink-500 h-full rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div class="flex justify-between">
<span class="text-gray-400">Current Repository:</span>
<span id="currentRepo" class="font-mono truncate ml-2">-</span>
</div>
<div class="flex justify-between">
<span class="text-gray-400">Scan Rate:</span>
<span id="scanRate" class="font-mono">0 repos/min</span>
</div>
</div>
</div>
<div class="glass-effect rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold">Discovered API Keys</h2>
<div class="flex items-center space-x-3">
<select id="filterStatus" class="bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-purple-500">
<option value="all">All Keys</option>
<option value="valid">Valid</option>
<option value="testing">Testing</option>
<option value="invalid">Invalid</option>
</select>
<button id="exportBtn" class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg text-sm transition-all duration-300">
<i data-feather="download" class="w-4 h-4 inline mr-2"></i>Export
</button>
</div>
</div>
<div id="keysContainer" class="space-y-3 max-h-96 overflow-y-auto">
<div class="text-center py-8 text-gray-400">
<i data-feather="inbox" class="w-12 h-12 mx-auto mb-3"></i>
<p>No keys found yet. Start scanning to discover API keys!</p>
</div>
</div>
</div>
</main>
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
<div class="glass-effect rounded-xl p-6 max-w-md w-full border border-gray-700">
<h3 class="text-xl font-bold mb-4">Scan Settings</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">GitHub Token (Optional)</label>
<input type="password" id="githubToken" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" placeholder="ghp_xxxxxxxxxxxx">
</div>
<div>
<label class="block text-sm font-medium mb-2">Max Repositories per Scan</label>
<input type="number" id="maxRepos" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="100" min="10" max="1000">
</div>
<div>
<label class="block text-sm font-medium mb-2">Delay between requests (ms)</label>
<input type="number" id="requestDelay" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="1000" min="100" max="5000">
</div>
<div>
<label class="block text-sm font-medium mb-2">Search Query</label>
<input type="text" id="searchQuery" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="API_KEY OR api_key OR token OR secret" placeholder="Search terms">
</div>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelSettings" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300">Cancel</button>
<button id="saveSettings" class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-lg transition-all duration-300">Save</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
<script>
VANTA.NET({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x667eea,
backgroundColor: 0x111827,
points: 8.00,
maxDistance: 25.00,
spacing: 18.00
});
feather.replace();
// Global variables
let isScanning = false;
let isPaused = false;
let scannedRepos = 0;
let foundKeys = [];
let validKeys = [];
let currentPage = 1;
let scanInterval;
// DOM elements
const startScanBtn = document.getElementById('startScanBtn');
const pauseScanBtn = document.getElementById('pauseScanBtn');
const settingsBtn = document.getElementById('settingsBtn');
const settingsModal = document.getElementById('settingsModal');
const cancelSettings = document.getElementById('cancelSettings');
const saveSettings = document.getElementById('saveSettings');
const exportBtn = document.getElementById('exportBtn');
const filterStatus = document.getElementById('filterStatus');
const keysContainer = document.getElementById('keysContainer');
// Settings
let settings = {
githubToken: '',
maxRepos: 100,
requestDelay: 1000,
searchQuery: 'API_KEY OR api_key OR token OR secret'
};
// API Key patterns
const apiPatterns = {
'aws': /AKIA[0-9A-Z]{16}/,
'github': /ghp_[0-9a-zA-Z]{36}/,
'google': /AIza[0-9A-Za-z\\-_]{35}/,
'slack': /xox[baprs]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}/,
'stripe': /sk_live_[0-9a-zA-Z]{24}/,
'sendgrid': /SG\.[0-9A-Za-z\-_]{22}\.[0-9A-Za-z\-_]{43}/,
'mailgun': /key-[0-9a-zA-Z]{32}/,
'twilio': /SK[0-9a-fA-F]{32}/,
'generic': /(?:api[_-]?key|apikey|token|secret)[\s:=]+["']?([a-zA-Z0-9\-_]{20,})["']?/i
};
// Event listeners
startScanBtn.addEventListener('click', () => {
if (isScanning) {
stopScan();
} else {
startScan();
}
});
pauseScanBtn.addEventListener('click', togglePause);
settingsBtn.addEventListener('click', () => settingsModal.classList.remove('hidden'));
cancelSettings.addEventListener('click', () => settingsModal.classList.add('hidden'));
saveSettings.addEventListener('click', saveSettingsHandler);
exportBtn.addEventListener('click', exportKeys);
filterStatus.addEventListener('change', filterKeys);
async function startScan() {
if (isScanning) return;
isScanning = true;
isPaused = false;
scannedRepos = 0;
foundKeys = [];
validKeys = [];
currentPage = 1;
updateUI();
startScanBtn.innerHTML = '<i data-feather="stop" class="w-4 h-4"></i><span>Stop Scan</span>';
pauseScanBtn.classList.remove('hidden');
feather.replace();
await scanGitHub();
}
async function scanGitHub() {
const scanStatus = document.getElementById('scanStatus');
const currentRepo = document.getElementById('currentRepo');
const scanRate = document.getElementById('scanRate');
scanStatus.textContent = 'Scanning';
scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium status-testing';
const startTime = Date.now();
while (isScanning && scannedRepos < settings.maxRepos) {
if (isPaused) {
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
try {
// Search for repositories
const searchUrl = `https://api.github.com/search/repositories?q=${encodeURIComponent(settings.searchQuery)}&sort=updated&order=desc&per_page=30&page=${currentPage}`;
const headers = settings.githubToken ? { 'Authorization': `token ${settings.githubToken}` } : {};
const response = await axios.get(searchUrl, { headers });
const repos = response.data.items;
if (repos.length === 0) {
currentPage = 1; // Reset to first page if no more results
continue;
}
for (const repo of repos) {
if (!isScanning || isPaused) break;
currentRepo.textContent = repo.full_name;
await scanRepository(repo, headers);
scannedRepos++;
updateUI();
// Calculate scan rate
const elapsedMinutes = (Date.now() - startTime) / 60000;
const rate = Math.round(scannedRepos / elapsedMinutes);
scanRate.textContent = `${rate} repos/min`;
await delay(settings.requestDelay);
}
currentPage++;
} catch (error) {
console.error('Error scanning GitHub:', error);
if (error.response && error.response.status === 403) {
scanStatus.textContent = 'Rate Limited';
scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium bg-red-600';
await delay(60000); // Wait 1 minute on rate limit
}
}
}
if (isScanning) {
completeScan();
}
}
async function scanRepository(repo, headers) {
try {
// Get repository contents
const contentsUrl = `https://api.github.com/repos/${repo.full_name}/git/trees/${repo.default_branch}?recursive=1`;
const contentsResponse = await axios.get(contentsUrl, { headers });
const files = contentsResponse.data.tree.filter(file => file.type === 'blob');
// Scan files for API keys
for (const file of files.slice(0, 10)) { // Limit to 10 files per repo
if (!isScanning || isPaused) break;
try {
const fileUrl = `https://api.github.com/repos/${repo.full_name}/git/blobs/${file.sha}`;
const fileResponse = await axios.get(fileUrl, { headers });
const content = atob(fileResponse.data.content);
scanContentForKeys(content, repo.full_name, file.path);
} catch (error) {
console.error(`Error scanning file ${file.path}:`, error);
}
await delay(100);
}
} catch (error) {
console.error(`Error accessing repository ${repo.full_name}:`, error);
}
}
function scanContentForKeys(content, repoName, filePath) {
for (const [provider, pattern] of Object.entries(apiPatterns)) {
const matches = content.match(new RegExp(pattern, 'g'));
if (matches) {
matches.forEach(match => {
const key = {
id: Date.now() + Math.random(),
key: match,
provider: provider,
repo: repoName,
file: filePath,
status: 'testing',
discoveredAt: new Date().toISOString()
};
foundKeys.push(key);
testKey(key);
updateKeysDisplay();
});
}
}
}
async function testKey(key) {
// Simulate key testing with different providers
setTimeout(() => {
// Randomly determine if key is valid (70% chance for demo)
key.status = Math.random() > 0.3 ? 'valid' : 'invalid';
if (key.status === 'valid') {
validKeys.push(key);
}
updateUI();
updateKeysDisplay();
}, Math.random() * 5000 + 2000);
}
function updateUI() {
document.getElementById('totalScanned').textContent = scannedRepos;
document.getElementById('keysFound').textContent = foundKeys.length;
document.getElementById('validKeys').textContent = validKeys.length;
const progress = Math.min((scannedRepos / settings.maxRepos) * 100, 100);
document.getElementById('progressPercent').textContent = `${Math.round(progress)}%`;
document.getElementById('progressBar').style.width = `${progress}%`;
}
function updateKeysDisplay() {
const filter = filterStatus.value;
const filteredKeys = foundKeys.filter(key =>
filter === 'all' || key.status === filter
);
if (filteredKeys.length === 0) {
keysContainer.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i data-feather="inbox" class="w-12 h-12 mx-auto mb-3"></i>
<p>No keys found${filter !== 'all' ? ` with status: ${filter}` : ''}</p>
</div>
`;
feather.replace();
return;
}
keysContainer.innerHTML = filteredKeys.map(key => `
<div class="key-card bg-gray-800 rounded-lg p-4 border border-gray-700 hover:border-purple-500">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center space-x-3">
<span class="px-2 py-1 rounded text-xs font-medium bg-purple-600">${key.provider}</span>
<span class="px-2 py-1 rounded text-xs font-medium ${
key.status === 'valid' ? 'status-active' :
key.status === 'invalid' ? 'status-inactive' : 'status-testing'
}">${key.status}</span>
</div>
<button onclick="copyKey('${key.key.replace(/'/g, "\\'")}')" class="text-gray-400 hover:text-white">
<i data-feather="copy" class="w-4 h-4"></i>
</button>
</div>
<div class="font-mono text-sm bg-gray-900 rounded p-2 mb-2 break-all">${key.key}</div>
<div class="text-xs text-gray-400">
<div class="flex items-center space-x-4">
<span><i data-feather="github" class="w-3 h-3 inline mr-1"></i>${key.repo}</span>
<span><i data-feather="file" class="w-3 h-3 inline mr-1"></i>${key.file.split('/').pop()}</span>
</div>
<div class="mt-1"><i data-feather="clock" class="w-3 h-3 inline mr-1"></i>${new Date(key.discoveredAt).toLocaleString()}</div>
</div>
</div>
`).join('');
feather.replace();
}
function filterKeys() {
updateKeysDisplay();
}
function copyKey(key) {
navigator.clipboard.writeText(key).then(() => {
Swal.fire({
icon: 'success',
title: 'Key Copied!',
text: 'API key copied to clipboard',
timer: 1500,
showConfirmButton: false
});
});
}
function exportKeys() {
const validOnly = validKeys.map(key => ({
key: key.key,
provider: key.provider,
repo: key.repo,
file: key.file,
discoveredAt: key.discoveredAt
}));
const dataStr = JSON.stringify(validOnly, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `api-keys-${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
}
function togglePause() {
isPaused = !isPaused;
pauseScanBtn.innerHTML = isPaused ?
'<i data-feather="play" class="w-4 h-4"></i>' :
'<i data-feather="pause" class="w-4 h-4"></i>';
feather.replace();
}
isScanning = false;
isPaused = false;
const scanStatus = document.getElementById('scanStatus');
scanStatus.textContent = 'Completed';
scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium status-active';
startScanBtn.innerHTML = '<i data-feather="play" class="w-4 h-4"></i><span>Start Scan</span>';
pauseScanBtn.classList.add('hidden');
feather.replace();
Swal.fire({
icon: 'success',
title: 'Scan Complete!',
text: `Scanned ${scannedRepos} repositories and found ${foundKeys.length} potential API keys (${validKeys.length} valid)`,
confirmButtonColor: '#667eea'
});
}
function stopScan() {
isScanning = false;
isPaused = false;
const scanStatus = document.getElementById('scanStatus');
scanStatus.textContent = 'Stopped';
scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium bg-gray-600';
startScanBtn.innerHTML = '<i data-feather="play" class="w-4 h-4"></i><span>Start Scan</span>';
pauseScanBtn.classList.add('hidden');
feather.replace();
}
function saveSettingsHandler() {
settings.githubToken = document.getElementById('githubToken').value;
settings.maxRepos = parseInt(document.getElementById('maxRepos').value);
settings.requestDelay = parseInt(document.getElementById('requestDelay').value);
settings.searchQuery = document.getElementById('searchQuery').value;
settingsModal.classList.add('hidden');
Swal.fire({
icon: 'success',
title: 'Settings Saved!',
text: 'Your scan settings have been updated',
timer: 1500,
showConfirmButton: false
});
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
updateUI();
updateKeysDisplay();
});
</script>
</body>
</html>