""" Simple Security Scanner - Gradio App with MCP Server A security vulnerability scanner that provides beginner-friendly explanations. """ import gradio as gr from src.scanner.pattern_detector import scan_patterns from src.scanner.sql_injection import scan_sql_injection from src.formatter import format_results # Sample vulnerable codes for demo SAMPLE_CODES = { "SQL Injection": '''import sqlite3 def get_user(username): conn = sqlite3.connect('users.db') cursor = conn.cursor() query = f"SELECT * FROM users WHERE username = '{username}'" cursor.execute(query) return cursor.fetchone() ''', "Hardcoded Secret": '''import requests API_KEY = "sk-1234567890abcdef" DATABASE_PASSWORD = "admin123" def connect(): return requests.get(f"https://api.example.com?key={API_KEY}") ''', "Path Traversal": '''import os def read_file(filename): base_path = "/var/www/uploads/" file_path = base_path + filename with open(file_path, 'r') as f: return f.read() ''', "Insecure Deserialization": '''import pickle import base64 def load_user_data(data): decoded = base64.b64decode(data) return pickle.loads(decoded) ''' } def format_results_markdown(results: dict) -> str: """Format scan results as Markdown for Gradio display.""" summary = results.get("summary", {}) vulns = results.get("vulnerabilities", []) output = [] output.append("## 📊 Scan Summary\n") output.append(f"**Total Issues Found:** {summary.get('total_issues', 0)}\n") output.append(f"- 🔴 CRITICAL: {summary.get('critical', 0)}") output.append(f"- 🟠 HIGH: {summary.get('high', 0)}") output.append(f"- 🟡 MEDIUM: {summary.get('medium', 0)}") output.append(f"- 🟢 LOW: {summary.get('low', 0)}\n") if not vulns: output.append("✅ **No security vulnerabilities found!**") return "\n".join(output) output.append("---\n") output.append("## 🔍 Detailed Findings\n") for i, vuln in enumerate(vulns, 1): severity = vuln.get("severity", "UNKNOWN") severity_emoji = {"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡", "LOW": "🟢"}.get(severity, "⚪") output.append(f"### {severity_emoji} [{i}] {vuln.get('title', 'Unknown Issue')}\n") output.append(f"**Severity:** {severity} ") output.append(f"**Line:** {vuln.get('line_number', 'N/A')}\n") code_snippet = vuln.get("code_snippet", "") if code_snippet: output.append(f"```python\n{code_snippet}\n```\n") explanation = vuln.get("explanation", {}) if explanation: output.append(f"**What:** {explanation.get('what', 'N/A')}\n") output.append(f"**Why it's dangerous:** {explanation.get('why', 'N/A')}\n") output.append(f"**How to fix:** {explanation.get('how_to_fix', 'N/A')}\n") example = explanation.get("example", "") if example: output.append(f"\n**Example:**\n```python\n{example}\n```\n") output.append("---\n") return "\n".join(output) def scan_code(code: str, severity_threshold: str = "MEDIUM") -> str: """ Scan Python code for security vulnerabilities. Args: code: Python source code to analyze severity_threshold: Minimum severity level (CRITICAL, HIGH, MEDIUM, LOW) Returns: Beginner-friendly explanation of found vulnerabilities """ if not code or not code.strip(): return "⚠️ Please enter some code to scan." all_findings = [] try: pattern_findings = scan_patterns("input.py", code) all_findings.extend(pattern_findings) except Exception: pass try: sql_findings = scan_sql_injection("input.py", code) all_findings.extend(sql_findings) except Exception: pass severity_order = {"CRITICAL": 4, "HIGH": 3, "MEDIUM": 2, "LOW": 1} threshold_value = severity_order.get(severity_threshold, 2) filtered_findings = [ f for f in all_findings if severity_order.get(f.get("severity", "LOW"), 1) >= threshold_value ] if not filtered_findings: return "✅ No security vulnerabilities found at the selected severity level!" results = format_results(filtered_findings, severity_threshold) return format_results_markdown(results) # Sample loader functions - use named functions instead of lambda def load_sample_sql_injection(): """Load SQL Injection sample code.""" return SAMPLE_CODES["SQL Injection"] def load_sample_hardcoded_secret(): """Load Hardcoded Secret sample code.""" return SAMPLE_CODES["Hardcoded Secret"] def load_sample_path_traversal(): """Load Path Traversal sample code.""" return SAMPLE_CODES["Path Traversal"] def load_sample_insecure_deserialization(): """Load Insecure Deserialization sample code.""" return SAMPLE_CODES["Insecure Deserialization"] # Mapping of sample names to loader functions SAMPLE_LOADERS = { "SQL Injection": load_sample_sql_injection, "Hardcoded Secret": load_sample_hardcoded_secret, "Path Traversal": load_sample_path_traversal, "Insecure Deserialization": load_sample_insecure_deserialization, } # Full UI with Blocks (not exposed to MCP) def create_blocks_ui(): """Create the Blocks UI - separated to avoid MCP exposure.""" with gr.Blocks( title="🔒 Simple Security Scanner", theme=gr.themes.Soft() ) as blocks_demo: gr.Markdown(""" # 🔒 Simple Security Scanner **Scans Python code for security vulnerabilities and provides beginner-friendly explanations.** Also available as an MCP (Model Context Protocol) server. """) with gr.Row(): with gr.Column(scale=2): code_input = gr.Code( label="Python Code Input", language="python", lines=15, ) with gr.Row(): severity_dropdown = gr.Dropdown( choices=["LOW", "MEDIUM", "HIGH", "CRITICAL"], value="MEDIUM", label="Minimum Severity", scale=1 ) scan_btn = gr.Button("🔍 Scan", variant="primary", scale=2) with gr.Column(scale=1): gr.Markdown("### 📝 Sample Code") for name, loader_fn in SAMPLE_LOADERS.items(): sample_btn = gr.Button(name, size="sm") sample_btn.click( fn=loader_fn, outputs=code_input, api_name=False # Do not expose to MCP ) output = gr.Markdown(label="Scan Results") scan_btn.click( fn=scan_code, inputs=[code_input, severity_dropdown], outputs=output, api_name=False # Do not expose to MCP ) gr.Markdown(""" --- ### 🛠️ Use as MCP Server Connect with Claude Desktop or other MCP clients: ```json { "mcpServers": { "security-scanner": { "command": "npx", "args": ["mcp-remote", "https://mcp-1st-birthday-simple-security-scanner.hf.space/gradio_api/mcp/sse"] } } } ``` """) return blocks_demo # MCP Tool - gr.Interface for MCP server exposure # Tool name must match pattern: ^[a-zA-Z0-9_-]{1,64}$ mcp_interface = gr.Interface( fn=scan_code, inputs=[ gr.Textbox(label="code", lines=10, placeholder="Paste Python code here..."), gr.Dropdown( choices=["LOW", "MEDIUM", "HIGH", "CRITICAL"], value="MEDIUM", label="severity_threshold" ) ], outputs=gr.Textbox(label="results"), title="Security Scanner", description="Scan Python code for security vulnerabilities and get beginner-friendly explanations.", api_name="scan_code" # MCP tool name: only alphanumeric, underscore, hyphen allowed ) # Create UI blocks_demo = create_blocks_ui() # Combine: TabbedInterface for both UI and MCP demo = gr.TabbedInterface( [blocks_demo, mcp_interface], ["🔒 Scanner UI", "🛠️ MCP Tool"], title="Simple Security Scanner" ) if __name__ == "__main__": demo.launch(mcp_server=True)