|
|
""" |
|
|
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_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) |
|
|
|
|
|
|
|
|
|
|
|
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"] |
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
output = gr.Markdown(label="Scan Results") |
|
|
|
|
|
scan_btn.click( |
|
|
fn=scan_code, |
|
|
inputs=[code_input, severity_dropdown], |
|
|
outputs=output, |
|
|
api_name=False |
|
|
) |
|
|
|
|
|
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_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" |
|
|
) |
|
|
|
|
|
|
|
|
blocks_demo = create_blocks_ui() |
|
|
|
|
|
|
|
|
demo = gr.TabbedInterface( |
|
|
[blocks_demo, mcp_interface], |
|
|
["π Scanner UI", "π οΈ MCP Tool"], |
|
|
title="Simple Security Scanner" |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(mcp_server=True) |