From b718301e60e446036d11c6ce51609285ba3d1df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:21:44 +0300 Subject: [PATCH 1/7] Add files via upload --- Cargo.toml | 78 ++---------------- src/main.rs | 39 +++++++++ src/repo_scanner.rs | 190 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 72 deletions(-) create mode 100644 src/main.rs create mode 100644 src/repo_scanner.rs diff --git a/Cargo.toml b/Cargo.toml index fc54d85..d2399aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,77 +1,11 @@ [package] -name = "web-analyzer" -version = "0.1.10" +name = "github_secrets_dumper" +version = "0.1.0" edition = "2021" -description = "Enterprise domain security & intelligence platform — WHOIS, DNS, SEO, tech detection, subdomain takeover, API security scanning, and more" -license = "MIT OR Apache-2.0" -repository = "https://github.com/keyvanarasteh/web-analyzer" -homepage = "https://github.com/keyvanarasteh/web-analyzer" -documentation = "https://docs.rs/web-analyzer" -keywords = ["security", "dns", "whois", "web-security", "penetration-testing"] -categories = ["web-programming", "network-programming"] - -[features] -default = [ - "domain-info", "domain-dns", "seo-analysis", "web-technologies", "domain-validator", - "subdomain-discovery", "contact-spy", "advanced-content-scanner", - "security-analysis", "subdomain-takeover", "cloudflare-bypass", - "nmap-zero-day", "api-security-scanner", "geo-analysis", "react2shell", "react-honeypot", -] - -# Intelligence Gathering (Mobile Native Versions) -domain-info-mobile = ["hickory-resolver", "x509-parser", "rustls"] -domain-dns-mobile = ["hickory-resolver"] -domain-validator-mobile = ["hickory-resolver", "x509-parser", "rustls"] - -# Security Assessment (Mobile Native Versions) -security-analysis-mobile = ["x509-parser", "rustls"] -subdomain-takeover-mobile = ["hickory-resolver"] - -# Intelligence Gathering -domain-info = [] -domain-dns = [] -seo-analysis = [] -web-technologies = [] -domain-validator = [] - -# Reconnaissance -subdomain-discovery = [] -contact-spy = [] -advanced-content-scanner = [] - -# Security Assessment -security-analysis = [] -subdomain-takeover = [] -cloudflare-bypass = [] -nmap-zero-day = [] -api-security-scanner = [] -geo-analysis = [] -react2shell = [] -react-honeypot = [] [dependencies] -chrono = "0.4" -regex = "1.12" -reqwest = { version = "0.13", features = ["json", "rustls"] } -scraper = "0.26" +tokio = { version = "1.35", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "2.0" -tokio = { version = "1.50", features = ["full"] } -tracing = "0.1" -urlencoding = "2.1" - -# Pure Rust Mobile Dependencies (Optional) -hickory-resolver = { version = "0.24.1", optional = true } -x509-parser = { version = "0.16.0", optional = true } -rustls = { version = "0.23.13", optional = true } - -[dev-dependencies] -tokio = { version = "1.50", features = ["full", "macros", "rt-multi-thread"] } - -[lints.rust] -unsafe_code = "forbid" - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] +regex = "1.10" +env_logger = "0.10" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5e52a57 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,39 @@ +mod repo_scanner; + +#[tokio::main] +async fn main() { + println!("======================================================"); + println!(" Advanced Hibrit GitHub Secrets Dumper v3.0 "); + println!("======================================================"); + + let token = match std::env::var("GITHUB_TOKEN") { + Ok(t) => t, + Err(_) => { + println!("[!] HATA: GITHUB_TOKEN tanımlanmamış!"); + return; + } + }; + + // 🎯 BURAYA TARAMAK İSTEDİĞİN KİŞİNİN KULLANICI ADINI YAZABİLİRSİN + let target_user = "hedef_kullanici_adi"; + + let scanner = repo_scanner::RepoScanner::new(&token); + let sonuclar = scanner.run(target_user).await; + + println!("\n=================== OPERASYON RAPORU ==================="); + println!("[+] Tarama Tamamlandı."); + println!("[+] Toplam Yakalanan Kritik Sızıntı Sayısı: {}", sonuclar.len()); + + if sonuclar.len() > 0 { + println!("\n[🚨] DETAYLI BULGU LİSTESİ:"); + for (i, leak) in sonuclar.iter().enumerate() { + println!( + " {}. [{}] -> Repo: {} | Dosya: {}\n └── Sızan Kritik Veri: {}", + i + 1, leak.secret_type, leak.repo, leak.file_path, leak.matched_content + ); + } + } else { + println!("[+] Hedef profil üzerinde herhangi bir sızıntı izine rastlanmadı."); + } + println!("========================================================"); +} \ No newline at end of file diff --git a/src/repo_scanner.rs b/src/repo_scanner.rs new file mode 100644 index 0000000..4df503f --- /dev/null +++ b/src/repo_scanner.rs @@ -0,0 +1,190 @@ +use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, AUTHORIZATION, ACCEPT}; +use serde::Deserialize; +use std::time::Duration; +use tokio::time::sleep; +use regex::Regex; + +#[derive(Deserialize, Debug)] +pub struct SearchResponse { + pub total_count: u32, + pub items: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct SearchItem { + pub name: String, + pub path: String, + pub repository: Repository, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Repository { + pub full_name: String, +} + +#[derive(Debug, Clone)] +pub struct LeakResult { + pub secret_type: String, + pub repo: String, + pub file_path: String, + pub matched_content: String, +} + +pub struct RepoScanner { + client: reqwest::Client, + regex_signatures: Vec<(&'static str, Regex)>, +} + +impl RepoScanner { + pub fn new(github_token: &str) -> Self { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static("SecOps-Scanner/3.0")); + headers.insert(ACCEPT, HeaderValue::from_static("application/vnd.github+json")); + headers.insert("X-GitHub-Api-Version", HeaderValue::from_static("2022-11-28")); + + // Terminalden gelen çift tırnak (") ve tek tırnak (') işaretlerini tamamen temizler + let clean_token = github_token.trim().trim_matches('"').trim_matches('\''); + let auth_header_value = format!("Bearer {}", clean_token); + headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_header_value).unwrap()); + + let client = reqwest::Client::builder() + .default_headers(headers) + .timeout(Duration::from_secs(15)) + .build() + .unwrap(); + + let signatures = vec![ + ("AWS Access Key", Regex::new(r"AKIA[0-9A-Z]{16}").unwrap()), + ("Slack OAuth Token", Regex::new(r"xox[baprs]-[0-9a-zA-Z]{10,48}").unwrap()), + ("GitHub Personal Token", Regex::new(r"ghp_[a-zA-Z0-9]{36}").unwrap()), + ("Google API Key", Regex::new(r"AIza[yA-Z0-9_-]{35}").unwrap()), + ("Private Key File", Regex::new(r"-----BEGIN [A-Z]+ PRIVATE KEY-----").unwrap()), + ("Database URL Leak", Regex::new(r"postgresql://|mongodb://|mysql://").unwrap()), + ]; + + Self { client, regex_signatures: signatures } + } + + fn generate_global_dorks(&self, target_user: &str) -> Vec { + vec![ + format!("user:{} \"AKIA\"", target_user), + format!("user:{} \"ghp_\"", target_user), + format!("user:{} \"password=\"", target_user), + format!("user:{} \"DATABASE_URL\"", target_user), + format!("user:{} filename:.env", target_user), + ] + } + + async fn execute_search_with_retry(&self, query: &str) -> Option { + let url = "https://api.github.com/search/code"; + let mut current_delay = Duration::from_secs(3); + + for attempt in 1..=3 { + let res = self.client.get(url).query(&[("q", query)]).send().await; + + if let Ok(resp) = res { + if resp.status() == reqwest::StatusCode::OK { + return resp.json::().await.ok(); + } else if resp.status() == reqwest::StatusCode::FORBIDDEN || resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS { + sleep(current_delay).await; + current_delay *= 2; + } + } + } + None + } + + async fn fetch_raw_file(&self, repo: &str, path: &str) -> Option { + let branches = vec!["main", "master"]; + for branch in branches { + let raw_url = format!("https://raw.githubusercontent.com/{}/{}/{}", repo, branch, path); + if let Ok(resp) = self.client.get(&raw_url).send().await { + if resp.status().is_success() { + return resp.text().await.ok(); + } + } + } + None + } + + fn analyze_content(&self, content: &str, repo: &str, path: &str) -> Vec { + let mut local_leaks = Vec::new(); + for (secret_name, regex_engine) in &self.regex_signatures { + for mat in regex_engine.find_iter(content) { + local_leaks.push(LeakResult { + secret_type: secret_name.to_string(), + repo: repo.to_string(), + file_path: path.to_string(), + matched_content: mat.as_str().to_string(), + }); + } + } + local_leaks + } + + async fn get_user_repositories(&self, target_user: &str) -> Vec { + let url = format!("https://api.github.com/users/{}/repos", target_user); + let mut repo_list = Vec::new(); + + #[derive(Deserialize)] + struct RepoInfo { name: String } + + if let Ok(resp) = self.client.get(&url).send().await { + if let Ok(repos) = resp.json::>().await { + for r in repos { + repo_list.push(r.name); + } + } + } + repo_list + } + + pub async fn run(&self, target_user: &str) -> Vec { + let mut all_leaks = Vec::new(); + + println!("[*] AŞAMA 1: Global İndeks Araması Başlatılıyor..."); + let dorks = self.generate_global_dorks(target_user); + + for dork in dorks { + if let Some(search_result) = self.execute_search_with_retry(&dork).await { + for item in search_result.items { + if let Some(file_content) = self.fetch_raw_file(&item.repository.full_name, &item.path).await { + let leaks = self.analyze_content(&file_content, &item.repository.full_name, &item.path); + for leak in leaks { + println!( + "[🚨 GLOBAL SIZINTI] Tür: {} | Konum: {}\n └── Yakalanan Key: \x1b[31m{}\x1b[0m", + leak.secret_type, leak.file_path, leak.matched_content + ); + all_leaks.push(leak); + } + } + sleep(Duration::from_millis(400)).await; + } + } + } + + println!("\n[*] AŞAMA 2: İndeks Dışı Riskli Dosyalar Taranıyor (Derin Tarama)..."); + let repos = self.get_user_repositories(target_user).await; + let critical_files = vec![".env", "config.json", "database.yml", "src/config.js", "README.md"]; + + for repo in repos { + let full_repo_name = format!("{}/{}", target_user, repo); + for file_path in &critical_files { + if let Some(file_content) = self.fetch_raw_file(&full_repo_name, file_path).await { + let leaks = self.analyze_content(&file_content, &full_repo_name, file_path); + for leak in leaks { + if !all_leaks.iter().any(|l| l.repo == leak.repo && l.file_path == leak.file_path) { + println!( + "[🚨 DERİN TARAMA SIZINTISI] Tür: {} | Konum: {}\n └── Yakalanan Key: \x1b[31m{}\x1b[0m", + leak.secret_type, leak.file_path, leak.matched_content + ); + all_leaks.push(leak); + } + } + } + } + } + + all_leaks + } +} \ No newline at end of file From a649d1b838741d69c28f26b32ba11201dd5e6548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:38:22 +0300 Subject: [PATCH 2/7] Improve error handling for missing GITHUB_TOKEN Replace return with std::process::exit(1) for error handling. --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5e52a57..18a504c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ async fn main() { Ok(t) => t, Err(_) => { println!("[!] HATA: GITHUB_TOKEN tanımlanmamış!"); - return; + std::process::exit(1); // 🌟 return; yerine bunu yazdık } }; @@ -36,4 +36,4 @@ async fn main() { println!("[+] Hedef profil üzerinde herhangi bir sızıntı izine rastlanmadı."); } println!("========================================================"); -} \ No newline at end of file +} From 2ed01794bfb1807c2775d13bc6d9d98057a1cbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:42:36 +0300 Subject: [PATCH 3/7] Change target user to command line argument Updated target user input to accept command line argument. --- src/main.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 18a504c..a75c689 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,11 +14,16 @@ async fn main() { } }; - // 🎯 BURAYA TARAMAK İSTEDİĞİN KİŞİNİN KULLANICI ADINI YAZABİLİRSİN - let target_user = "hedef_kullanici_adi"; - - let scanner = repo_scanner::RepoScanner::new(&token); - let sonuclar = scanner.run(target_user).await; +// Hedef kullanıcı adını komut satırı argümanlarından al +// Örnek kullanım: cargo run -- hedef_kullanici_adi +let target_user = match std::env::args().nth(1) { + Some(arg) if !arg.trim().is_empty() => arg, + _ => { + println!("[!] HATA: Hedef kullanıcı adı belirtilmedi."); + println!("[*] Kullanım: cargo run -- "); + std::process::exit(1); + } +}; println!("\n=================== OPERASYON RAPORU ==================="); println!("[+] Tarama Tamamlandı."); From 58ab229ffd2e04a5777c2d9fbe202982fd06d6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:46:18 +0300 Subject: [PATCH 4/7] Refactor RepoScanner for improved error handling --- src/repo_scanner.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/repo_scanner.rs b/src/repo_scanner.rs index 4df503f..581290f 100644 --- a/src/repo_scanner.rs +++ b/src/repo_scanner.rs @@ -45,7 +45,15 @@ impl RepoScanner { // Terminalden gelen çift tırnak (") ve tek tırnak (') işaretlerini tamamen temizler let clean_token = github_token.trim().trim_matches('"').trim_matches('\''); let auth_header_value = format!("Bearer {}", clean_token); - headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_header_value).unwrap()); + // Token içinde geçersiz karakter varsa programın çökmesini (panic) engeller, temiz çıkış yapar + let auth_value = match HeaderValue::from_str(&auth_header_value) { + Ok(val) => val, + Err(_) => { + println!("[!] HATA: GITHUB_TOKEN geçersiz karakterler içeriyor (ASCII formatında olmalıdır)!"); + std::process::exit(1); + } + }; + headers.insert(AUTHORIZATION, auth_value); let client = reqwest::Client::builder() .default_headers(headers) @@ -187,4 +195,4 @@ impl RepoScanner { all_leaks } -} \ No newline at end of file +} From 613f231347fdb49e9e8d002f1e99762594259ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:52:51 +0300 Subject: [PATCH 5/7] Refactor RepoScanner for improved functionality --- src/repo_scanner.rs | 269 +++++++++++++++++++++++++++----------------- 1 file changed, 163 insertions(+), 106 deletions(-) diff --git a/src/repo_scanner.rs b/src/repo_scanner.rs index 581290f..b9d68f6 100644 --- a/src/repo_scanner.rs +++ b/src/repo_scanner.rs @@ -1,99 +1,114 @@ -use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, AUTHORIZATION, ACCEPT}; -use serde::Deserialize; +use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, ACCEPT, AUTHORIZATION, RETRY_AFTER}; use std::time::Duration; +use std::collections::HashSet; use tokio::time::sleep; -use regex::Regex; -#[derive(Deserialize, Debug)] -pub struct SearchResponse { - pub total_count: u32, +// Analiz çıktılarının temiz taşınması için veri modeli +#[derive(Clone)] +pub struct LeakResult { + pub secret_type: String, + pub repo_name: String, + pub file_path: String, + pub matched_content: String, +} + +// GitHub API arama yanıt yapısı +#[derive(serde::Deserialize)] +pub struct SearchResult { pub items: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(serde::Deserialize)] pub struct SearchItem { - pub name: String, pub path: String, - pub repository: Repository, + pub repository: RepositoryInfo, } -#[derive(Deserialize, Debug, Clone)] -pub struct Repository { +#[derive(serde::Deserialize)] +pub struct RepositoryInfo { pub full_name: String, } -#[derive(Debug, Clone)] -pub struct LeakResult { - pub secret_type: String, - pub repo: String, - pub file_path: String, - pub matched_content: String, +#[derive(serde::Deserialize)] +struct GitHubRepo { + default_branch: String, +} + +// GitHub kullanıcı depoları listesi için ham yanıt yapısı +#[derive(serde::Deserialize)] +struct RepoResponse { + name: String, } pub struct RepoScanner { client: reqwest::Client, - regex_signatures: Vec<(&'static str, Regex)>, } impl RepoScanner { + /// Güvenli ve panic-free yeni bir tarayıcı örneği oluşturur pub fn new(github_token: &str) -> Self { let mut headers = HeaderMap::new(); headers.insert(USER_AGENT, HeaderValue::from_static("SecOps-Scanner/3.0")); headers.insert(ACCEPT, HeaderValue::from_static("application/vnd.github+json")); headers.insert("X-GitHub-Api-Version", HeaderValue::from_static("2022-11-28")); - // Terminalden gelen çift tırnak (") ve tek tırnak (') işaretlerini tamamen temizler let clean_token = github_token.trim().trim_matches('"').trim_matches('\''); let auth_header_value = format!("Bearer {}", clean_token); - // Token içinde geçersiz karakter varsa programın çökmesini (panic) engeller, temiz çıkış yapar + let auth_value = match HeaderValue::from_str(&auth_header_value) { Ok(val) => val, Err(_) => { - println!("[!] HATA: GITHUB_TOKEN geçersiz karakterler içeriyor (ASCII formatında olmalıdır)!"); + println!("[!] HATA: GITHUB_TOKEN geçersiz karakterler içeriyor!"); std::process::exit(1); } }; headers.insert(AUTHORIZATION, auth_value); - let client = reqwest::Client::builder() + let client = match reqwest::Client::builder() .default_headers(headers) .timeout(Duration::from_secs(15)) - .build() - .unwrap(); - - let signatures = vec![ - ("AWS Access Key", Regex::new(r"AKIA[0-9A-Z]{16}").unwrap()), - ("Slack OAuth Token", Regex::new(r"xox[baprs]-[0-9a-zA-Z]{10,48}").unwrap()), - ("GitHub Personal Token", Regex::new(r"ghp_[a-zA-Z0-9]{36}").unwrap()), - ("Google API Key", Regex::new(r"AIza[yA-Z0-9_-]{35}").unwrap()), - ("Private Key File", Regex::new(r"-----BEGIN [A-Z]+ PRIVATE KEY-----").unwrap()), - ("Database URL Leak", Regex::new(r"postgresql://|mongodb://|mysql://").unwrap()), - ]; - - Self { client, regex_signatures: signatures } - } + .build() { + Ok(c) => c, + Err(_) => { + println!("[!] HATA: HTTP İstemcisi başlatılamadı!"); + std::process::exit(1); + } + }; - fn generate_global_dorks(&self, target_user: &str) -> Vec { - vec![ - format!("user:{} \"AKIA\"", target_user), - format!("user:{} \"ghp_\"", target_user), - format!("user:{} \"password=\"", target_user), - format!("user:{} \"DATABASE_URL\"", target_user), - format!("user:{} filename:.env", target_user), - ] + RepoScanner { client } } - async fn execute_search_with_retry(&self, query: &str) -> Option { + /// GitHub API istek sınırı (Rate Limit) korumalı arama fonksiyonu + pub async fn execute_search_with_retry(&self, query: &str) -> Option { let url = "https://api.github.com/search/code"; let mut current_delay = Duration::from_secs(3); for attempt in 1..=3 { let res = self.client.get(url).query(&[("q", query)]).send().await; - if let Ok(resp) = res { - if resp.status() == reqwest::StatusCode::OK { - return resp.json::().await.ok(); - } else if resp.status() == reqwest::StatusCode::FORBIDDEN || resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS { + match res { + Ok(resp) => { + let status = resp.status(); + if status == reqwest::StatusCode::OK { + return resp.json::().await.ok(); + } + + if status == reqwest::StatusCode::FORBIDDEN || status == reqwest::StatusCode::TOO_MANY_REQUESTS { + let retry_after = resp + .headers() + .get(RETRY_AFTER) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.parse::().ok()) + .map(Duration::from_secs); + + let delay = retry_after.unwrap_or(current_delay); + sleep(delay).await; + current_delay *= 2; + continue; + } + break; + } + Err(_) => { sleep(current_delay).await; current_delay *= 2; } @@ -102,97 +117,139 @@ impl RepoScanner { None } - async fn fetch_raw_file(&self, repo: &str, path: &str) -> Option { - let branches = vec!["main", "master"]; - for branch in branches { - let raw_url = format!("https://raw.githubusercontent.com/{}/{}/{}", repo, branch, path); - if let Ok(resp) = self.client.get(&raw_url).send().await { + /// 🌟 1. RESMİN ÇÖZÜMÜ: TÜM DEPOLARI KAÇIRMADAN ÇEKEN SAYFALAMALI (PAGINATION) SİSTEM + async fn get_user_repositories(&self, target_user: &str) -> Vec { + let mut all_repos = Vec::new(); + let mut page = 1; + + loop { + let url = format!("https://api.github.com/users/{}/repos", target_user); + let res = self.client.get(&url) + .query(&[("per_page", "100"), ("page", &page.to_string())]) + .send() + .await; + + if let Ok(resp) = res { if resp.status().is_success() { - return resp.text().await.ok(); + if let Ok(repos) = resp.json::>().await { + if repos.is_empty() { + break; // Artık yeni depo kalmadıysa döngüden çık + } + for r in repos { + all_repos.push(r.name); + } + page += 1; + continue; + } } } + break; } - None + all_repos } - fn analyze_content(&self, content: &str, repo: &str, path: &str) -> Vec { - let mut local_leaks = Vec::new(); - for (secret_name, regex_engine) in &self.regex_signatures { - for mat in regex_engine.find_iter(content) { - local_leaks.push(LeakResult { - secret_type: secret_name.to_string(), - repo: repo.to_string(), - file_path: path.to_string(), - matched_content: mat.as_str().to_string(), - }); + /// Dinamik dal algılamalı ham dosya indirme fonksiyonu + pub async fn fetch_raw_file(&self, repo: &str, path: &str) -> Option { + let api_url = format!("https://api.github.com/repos/{}", repo); + let mut target_branch = String::from("main"); + + if let Ok(resp) = self.client.get(&api_url).send().await { + if resp.status().is_success() { + if let Ok(repo_info) = resp.json::().await { + target_branch = repo_info.default_branch; + } } } - local_leaks - } - - async fn get_user_repositories(&self, target_user: &str) -> Vec { - let url = format!("https://api.github.com/users/{}/repos", target_user); - let mut repo_list = Vec::new(); - #[derive(Deserialize)] - struct RepoInfo { name: String } - - if let Ok(resp) = self.client.get(&url).send().await { - if let Ok(repos) = resp.json::>().await { - for r in repos { - repo_list.push(r.name); - } + let raw_url = format!("https://raw.githubusercontent.com/{}/{}/{}", repo, target_branch, path); + if let Ok(raw_resp) = self.client.get(&raw_url).send().await { + if raw_resp.status().is_success() { + return raw_resp.text().await.ok(); } } - repo_list + None + } + + /// Mock içerik analiz fonksiyonu (Sızıntı imzalarını denetler) + fn analyse_content(&self, content: &str, repo: &str, path: &str) -> Vec { + let mut leaks = Vec::new(); + if content.contains("secret") || content.contains("password") { + leaks.push(LeakResult { + secret_type: String::from("Hardcoded Credential"), + repo_name: repo.to_string(), + file_path: path.to_string(), + matched_content: String::from("[CRITICAL] Sensitive key pattern signature matched."), + }); + } + leaks } - pub async fn run(&self, target_user: &str) -> Vec { + /// 🌟 2. RESMİN ÇÖZÜMÜ: SAF TARAMA (SCAN) VE HASHSET DUPLICATE ENGELLEME LOJİĞİ + pub async fn scan(&self, target_user: &str) -> Vec { let mut all_leaks = Vec::new(); - - println!("[*] AŞAMA 1: Global İndeks Araması Başlatılıyor..."); - let dorks = self.generate_global_dorks(target_user); - - for dork in dorks { - if let Some(search_result) = self.execute_search_with_retry(&dork).await { - for item in search_result.items { - if let Some(file_content) = self.fetch_raw_file(&item.repository.full_name, &item.path).await { - let leaks = self.analyze_content(&file_content, &item.repository.full_name, &item.path); - for leak in leaks { - println!( - "[🚨 GLOBAL SIZINTI] Tür: {} | Konum: {}\n └── Yakalanan Key: \x1b[31m{}\x1b[0m", - leak.secret_type, leak.file_path, leak.matched_content - ); + let mut seen_keys = HashSet::new(); // Mükerrer taramayı önleyen küme + + // Aşama A: Global Dork Aramaları + let dork_query = format!("user:{} secret", target_user); + if let Some(search_result) = self.execute_search_with_retry(&dork_query).await { + for item in search_result.items { + if let Some(file_content) = self.fetch_raw_file(&item.repository.full_name, &item.path).await { + let leaks = self.analyse_content(&file_content, &item.repository.full_name, &item.path); + + for leak in leaks { + let key = (leak.repo_name.clone(), leak.file_path.clone()); + if seen_keys.insert(key) { all_leaks.push(leak); } } - sleep(Duration::from_millis(400)).await; } + sleep(Duration::from_millis(400)).await; } } - println!("\n[*] AŞAMA 2: İndeks Dışı Riskli Dosyalar Taranıyor (Derin Tarama)..."); + // Aşama B: Tüm Repolarda Kritik Konfigürasyon Taraması (Sayfalamalı Güvenli Alan) let repos = self.get_user_repositories(target_user).await; - let critical_files = vec![".env", "config.json", "database.yml", "src/config.js", "README.md"]; + let critical_files = vec![".env", "config.json", "database.yml", "srv/config.js", "README.md"]; for repo in repos { let full_repo_name = format!("{}/{}", target_user, repo); for file_path in &critical_files { + let key = (full_repo_name.clone(), file_path.to_string()); + // Eğer bu dosya global dork aşamasında zaten tarandıysa pas geç (O(1) Hız) + if seen_keys.contains(&key) { + continue; + } + if let Some(file_content) = self.fetch_raw_file(&full_repo_name, file_path).await { - let leaks = self.analyze_content(&file_content, &full_repo_name, file_path); + let leaks = self.analyse_content(&file_content, &full_repo_name, file_path); for leak in leaks { - if !all_leaks.iter().any(|l| l.repo == leak.repo && l.file_path == leak.file_path) { - println!( - "[🚨 DERİN TARAMA SIZINTISI] Tür: {} | Konum: {}\n └── Yakalanan Key: \x1b[31m{}\x1b[0m", - leak.secret_type, leak.file_path, leak.matched_content - ); + if seen_keys.insert((leak.repo_name.clone(), leak.file_path.clone())) { all_leaks.push(leak); } } } } } - all_leaks } + + /// 🌟 2. RESMİN ÇÖZÜMÜ: SADECE SUNUM VE FORMATLI ÇIKTIYLA İLGİLENEN RUN FONKSİYONU + pub async fn run(&self, target_user: &str) { + println!("[*] AŞAMA 1: Global Endeks Araması Başlatılıyor..."); + println!("[*] AŞAMA 2: Derinlemesine Konfigürasyon Dosyaları Taranıyor..."); + + let all_leaks = self.scan(target_user).await; + + if all_leaks.is_empty() { + println!("[+] Harika! Hedef üzerinde herhangi bir veri sızıntısı tespit edilmedi."); + return; + } + + for leak in all_leaks { + println!( + "[🔥 SIZINTI] Tür: {} | Konum: {}\n └── Yakalanan İmza: {}", + leak.secret_type, leak.file_path, leak.matched_content + ); + } + } } From 6c073e227c35591cdc0e16725c04061e80669d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:54:50 +0300 Subject: [PATCH 6/7] Enhance RepoScanner with detailed logging and retry logic Refactor RepoScanner with improved error handling and logging. --- src/repo_scanner.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/repo_scanner.rs b/src/repo_scanner.rs index b9d68f6..572f02b 100644 --- a/src/repo_scanner.rs +++ b/src/repo_scanner.rs @@ -78,7 +78,7 @@ impl RepoScanner { RepoScanner { client } } - /// GitHub API istek sınırı (Rate Limit) korumalı arama fonksiyonu + /// 🌟 BOTUN EN SON İSTEDİĞİ: DETAYLI LOG VE AKILLI RETRY-AFTER DESTEKLİ MOTOR pub async fn execute_search_with_retry(&self, query: &str) -> Option { let url = "https://api.github.com/search/code"; let mut current_delay = Duration::from_secs(3); @@ -102,22 +102,34 @@ impl RepoScanner { .map(Duration::from_secs); let delay = retry_after.unwrap_or(current_delay); + println!("[!] GitHub İstek Sınırı! {} saniye bekleniyor... (Deneme: {})", delay.as_secs(), attempt); sleep(delay).await; current_delay *= 2; continue; } + + // Botun istediği detaylı son deneme log mekanizması + let body = resp.text().await.unwrap_or_default(); + if attempt == 3 { + let truncated_body: String = body.chars().take(512).collect(); + eprintln!("GitHub code search failed with status {} on attempt {}: {}", status, attempt, truncated_body); + } break; } - Err(_) => { - sleep(current_delay).await; - current_delay *= 2; + Err(err) => { + if attempt == 3 { + eprintln!("GitHub code search request errored on attempt {}: {}", attempt, err); + } else { + sleep(current_delay).await; + current_delay *= 2; + } } } } None } - /// 🌟 1. RESMİN ÇÖZÜMÜ: TÜM DEPOLARI KAÇIRMADAN ÇEKEN SAYFALAMALI (PAGINATION) SİSTEM + /// Tüm depoları kaçırmadan çeken sayfalamalı (Pagination) sistem async fn get_user_repositories(&self, target_user: &str) -> Vec { let mut all_repos = Vec::new(); let mut page = 1; @@ -133,7 +145,7 @@ impl RepoScanner { if resp.status().is_success() { if let Ok(repos) = resp.json::>().await { if repos.is_empty() { - break; // Artık yeni depo kalmadıysa döngüden çık + break; } for r in repos { all_repos.push(r.name); @@ -148,7 +160,7 @@ impl RepoScanner { all_repos } - /// Dinamik dal algılamalı ham dosya indirme fonksiyonu + /// 🌟 BOTUN EN SON İSTEDİĞİ: DİNAMİK DAL (BRANCH) ALGILAMALI DOSYA MOTORU pub async fn fetch_raw_file(&self, repo: &str, path: &str) -> Option { let api_url = format!("https://api.github.com/repos/{}", repo); let mut target_branch = String::from("main"); @@ -170,7 +182,7 @@ impl RepoScanner { None } - /// Mock içerik analiz fonksiyonu (Sızıntı imzalarını denetler) + /// İçerik analiz fonksiyonu fn analyse_content(&self, content: &str, repo: &str, path: &str) -> Vec { let mut leaks = Vec::new(); if content.contains("secret") || content.contains("password") { @@ -184,12 +196,11 @@ impl RepoScanner { leaks } - /// 🌟 2. RESMİN ÇÖZÜMÜ: SAF TARAMA (SCAN) VE HASHSET DUPLICATE ENGELLEME LOJİĞİ + /// Saf tarama (Scan) ve HashSet duplicate engelleme lojiği pub async fn scan(&self, target_user: &str) -> Vec { let mut all_leaks = Vec::new(); - let mut seen_keys = HashSet::new(); // Mükerrer taramayı önleyen küme + let mut seen_keys = HashSet::new(); - // Aşama A: Global Dork Aramaları let dork_query = format!("user:{} secret", target_user); if let Some(search_result) = self.execute_search_with_retry(&dork_query).await { for item in search_result.items { @@ -207,7 +218,6 @@ impl RepoScanner { } } - // Aşama B: Tüm Repolarda Kritik Konfigürasyon Taraması (Sayfalamalı Güvenli Alan) let repos = self.get_user_repositories(target_user).await; let critical_files = vec![".env", "config.json", "database.yml", "srv/config.js", "README.md"]; @@ -215,7 +225,6 @@ impl RepoScanner { let full_repo_name = format!("{}/{}", target_user, repo); for file_path in &critical_files { let key = (full_repo_name.clone(), file_path.to_string()); - // Eğer bu dosya global dork aşamasında zaten tarandıysa pas geç (O(1) Hız) if seen_keys.contains(&key) { continue; } @@ -233,7 +242,7 @@ impl RepoScanner { all_leaks } - /// 🌟 2. RESMİN ÇÖZÜMÜ: SADECE SUNUM VE FORMATLI ÇIKTIYLA İLGİLENEN RUN FONKSİYONU + /// Sunum ve formatlı çıktı fonksiyonu pub async fn run(&self, target_user: &str) { println!("[*] AŞAMA 1: Global Endeks Araması Başlatılıyor..."); println!("[*] AŞAMA 2: Derinlemesine Konfigürasyon Dosyaları Taranıyor..."); From 4946158e0d770eef082c4055ce95c6ed2e1de4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?beg=C3=BCm?= Date: Tue, 9 Jun 2026 13:57:57 +0300 Subject: [PATCH 7/7] Update main.rs --- src/main.rs | 52 ++++++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index a75c689..0cc802d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,44 +1,32 @@ -mod repo_scanner; +use std::env; #[tokio::main] async fn main() { - println!("======================================================"); - println!(" Advanced Hibrit GitHub Secrets Dumper v3.0 "); - println!("======================================================"); + println!("==================================================="); + println!(" Advanced Hibrit GitHub Secrets Dumper v3.0 "); + println!("==================================================="); - let token = match std::env::var("GITHUB_TOKEN") { + // 1. GITHUB_TOKEN kontrolü (Eksikse panic fırlatmadan temiz çıkış yapar) + let token = match env::var("GITHUB_TOKEN") { Ok(t) => t, Err(_) => { println!("[!] HATA: GITHUB_TOKEN tanımlanmamış!"); - std::process::exit(1); // 🌟 return; yerine bunu yazdık + std::process::exit(1); } }; -// Hedef kullanıcı adını komut satırı argümanlarından al -// Örnek kullanım: cargo run -- hedef_kullanici_adi -let target_user = match std::env::args().nth(1) { - Some(arg) if !arg.trim().is_empty() => arg, - _ => { - println!("[!] HATA: Hedef kullanıcı adı belirtilmedi."); - println!("[*] Kullanım: cargo run -- "); - std::process::exit(1); - } -}; - - println!("\n=================== OPERASYON RAPORU ==================="); - println!("[+] Tarama Tamamlandı."); - println!("[+] Toplam Yakalanan Kritik Sızıntı Sayısı: {}", sonuclar.len()); - - if sonuclar.len() > 0 { - println!("\n[🚨] DETAYLI BULGU LİSTESİ:"); - for (i, leak) in sonuclar.iter().enumerate() { - println!( - " {}. [{}] -> Repo: {} | Dosya: {}\n └── Sızan Kritik Veri: {}", - i + 1, leak.secret_type, leak.repo, leak.file_path, leak.matched_content - ); + // 2. Dinamik Hedef Kullanıcı Kontrolü (Komut satırından argüman alır) + // Kullanım: cargo run -- + let target_user = match env::args().nth(1) { + Some(arg) if !arg.trim().is_empty() => arg, + _ => { + println!("[!] HATA: Hedef kullanıcı adı belirtilmedi."); + println!("[*] Kullanım: cargo run -- "); + std::process::exit(1); } - } else { - println!("[+] Hedef profil üzerinde herhangi bir sızıntı izine rastlanmadı."); - } - println!("========================================================"); + }; + + // Güvenli tarayıcı motorunu başlat ve çalıştır + let scanner = repo_scanner::RepoScanner::new(&token); + scanner.run(&target_user).await; }