File size: 8,019 Bytes
d9a89da
 
 
 
c05796a
 
d9a89da
 
 
 
 
 
 
 
 
 
 
 
b9db815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9a89da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9db815
 
 
d9a89da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c05796a
d9a89da
 
c05796a
 
 
 
 
 
 
 
d9a89da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9db815
d9a89da
 
 
 
 
 
b9db815
 
 
 
 
 
 
 
 
 
d9a89da
 
 
b9db815
d9a89da
 
 
b9db815
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import os
import asyncio
import gradio as gr
from playwright.async_api import async_playwright
from google import genai
from google.genai import types
from groq import Groq
from datetime import datetime
from bs4 import BeautifulSoup
import traceback
from fpdf import FPDF
import re

# --- CONSTANTS & CONFIG ---
SKILL_FILE_PATH = "ADVANCED_QA_AGENT_SKILL.md"
REPORTS_DIR = "reports"
os.makedirs(REPORTS_DIR, exist_ok=True)

# --- RATE LIMITING ---
IP_TRACKER = {}
MAX_REQUESTS_PER_HOUR = 5

def check_rate_limit(request: gr.Request):
    if request is None: return True
    ip = getattr(request.client, "host", "unknown")
    now = datetime.now()
    if ip not in IP_TRACKER:
        IP_TRACKER[ip] = []
    
    # Filter out requests older than 1 hour
    IP_TRACKER[ip] = [t for t in IP_TRACKER[ip] if (now - t).total_seconds() < 3600]
    
    if len(IP_TRACKER[ip]) >= MAX_REQUESTS_PER_HOUR:
        return False
    IP_TRACKER[ip].append(now)
    return True

# THEME FIX: Aggressively forcing white text for readability
CSS = """
body { background-color: #0b0f19; color: #e2e8f0; font-family: 'Inter', sans-serif; }
.gradio-container { background: rgba(15, 23, 42, 0.7) !important; backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 20px; }
.glass-panel { background: rgba(30, 41, 59, 0.7) !important; padding: 30px !important; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); }
/* AGGRESSIVE TEXT OVERRIDE */
.glass-panel *, .glass-panel p, .glass-panel li, .glass-panel span, .glass-panel div { 
    color: #ffffff !important; 
    font-size: 1.05rem !important;
}
.glass-panel h1, .glass-panel h2, .glass-panel h3 { 
    color: #60a5fa !important; 
    font-weight: 700 !important;
    margin-top: 1.5rem !important;
}
.primary-btn { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important; border: none !important; color: white !important; font-weight: 600 !important; }
.status-log { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: #94a3b8; background: #020617; padding: 15px; border: 1px solid #1e293b; max-height: 400px; overflow-y: auto; }
h1 { background: linear-gradient(to right, #60a5fa, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; font-size: 2.5rem; text-align: center; }
"""

class BugHunterAI:
    def __init__(self):
        self.gemini_key = os.environ.get("GEMINI_API_KEY")
        self.groq_key = os.environ.get("GROQ_API_KEY")
        self.skill_content = self._load_skill()
        self.logs = []

    def _load_skill(self):
        try:
            if os.path.exists(SKILL_FILE_PATH):
                with open(SKILL_FILE_PATH, "r", encoding="utf-8") as f:
                    return f.read()
            return "Skill file not found."
        except: return "Error loading skill."

    def log(self, message):
        timestamp = datetime.now().strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}"
        self.logs.append(log_entry)
        return "\n".join(self.logs)

    def generate_pdf(self, text, filename):
        try:
            pdf = FPDF()
            pdf.add_page()
            pdf.set_font("Helvetica", size=12)
            # Remove markdown syntax for cleaner PDF
            clean_text = re.sub(r'[*#_`]', '', text)
            pdf.multi_cell(0, 10, clean_text)
            path = os.path.join(REPORTS_DIR, filename)
            pdf.output(path)
            return path
        except Exception as e:
            print(f"PDF creation error: {e}")
            return None

    async def get_ai_response(self, prompt, system_instruction):
        try:
            if self.gemini_key:
                client = genai.Client(api_key=self.gemini_key)
                for model_name in ['gemini-3.1-pro-preview', 'gemini-2.5-flash', 'gemini-1.5-flash-latest']:
                    try:
                        response = await asyncio.to_thread(
                            client.models.generate_content,
                            model=model_name,
                            contents=prompt,
                            config=types.GenerateContentConfig(
                                system_instruction=system_instruction
                            )
                        )
                        return response.text
                    except: continue
            
            if self.groq_key:
                client = Groq(api_key=self.groq_key)
                for model_name in ['llama-4-scout', 'gemma-4-31b-it', 'llama-3.3-70b-versatile']:
                    try:
                        res = client.chat.completions.create(
                            messages=[{"role":"system","content":system_instruction},{"role":"user","content":prompt}],
                            model=model_name
                        )
                        return res.choices[0].message.content
                    except: continue
            return "No valid API config."
        except Exception as e: return f"Error: {str(e)}"

    async def run_audit(self, url):
        self.logs = []
        try:
            if not url.startswith("http"): url = "https://" + url
            yield self.log(f"πŸš€ AUDIT START: {url}"), "", None
            
            async with async_playwright() as p:
                yield self.log("🌐 Launching Browser..."), "", None
                browser = await p.chromium.launch(headless=True)
                page = await browser.new_page()
                await page.goto(url, wait_until="load", timeout=60000)
                
                yield self.log("πŸ“Έ Scanning..."), "", None
                content = await page.content()
                soup = BeautifulSoup(content, 'html.parser')
                for el in soup(["script", "style", "nav", "footer"]): el.decompose()
                
                yield self.log("🧠 Consulting Skills..."), "", None
                prompt = f"Target: {url}\n\nCONTENT:\n{soup.get_text()[:4000]}\n\nReport findings."
                report = await self.get_ai_response(prompt, self.skill_content)
                
                # Save PDF
                pdf_path = self.generate_pdf(report, f"audit_{datetime.now().strftime('%H%M%S')}.pdf")
                
                yield self.log("βœ… Audit Complete."), report, pdf_path
                await browser.close()
        except Exception as e:
            yield self.log(f"❌ ERROR: {str(e)}"), f"Audit Failed: {str(e)}", None

def create_ui():
    with gr.Blocks(css=CSS) as demo:
        gr.Markdown("<h1>BugHunter AI</h1>")
        with gr.Row():
            with gr.Column(scale=2):
                url_input = gr.Textbox(label="Website URL")
                with gr.Row():
                    captcha_input = gr.Textbox(label="Anti-Bot Check: What is 7 + 4?")
                btn = gr.Button("πŸš€ Run Audit", variant="primary", elem_classes=["primary-btn"])
                out_file = gr.File(label="πŸ“„ Download PDF Report")
            with gr.Column(scale=1):
                logs = gr.Markdown("### πŸ•’ Logs", elem_classes=["status-log"])
        report = gr.Markdown("### πŸ” Report", elem_classes=["glass-panel"])

        async def start(u, captcha, request: gr.Request):
            if captcha.strip() != "11":
                yield "❌ CAPTCHA Failed. Are you a bot?", "Audit Failed: Incorrect Captcha.", None
                return
                
            if not check_rate_limit(request):
                yield "❌ Rate Limit Exceeded.", "Audit Failed: You have exceeded the maximum number of requests per hour (5).", None
                return
                
            hunter = BugHunterAI()
            async for l, r, f in hunter.run_audit(u):
                yield l, r, f

        btn.click(start, inputs=[url_input, captcha_input], outputs=[logs, report, out_file])
    return demo

if __name__ == "__main__":
    demo = create_ui()
    demo.queue(default_concurrency_limit=2).launch(server_name="0.0.0.0", server_port=7860)