Spaces:
Running
Running
#!/usr/bin/env python3 | |
""" | |
MCP server for Bandit - a tool for finding common security issues in Python code | |
""" | |
import gradio as gr | |
import subprocess | |
import json | |
import os | |
import tempfile | |
from typing import Dict, List, Optional | |
from pathlib import Path | |
def bandit_scan( | |
code_input: str, | |
scan_type: str = "code", | |
severity_level: str = "low", | |
confidence_level: str = "low", | |
output_format: str = "json" | |
) -> Dict: | |
""" | |
Analyzes Python code for security issues using Bandit. | |
Args: | |
code_input (str): Python code for analysis or path to file/directory | |
scan_type (str): Scan type - 'code' for direct code or 'path' for file/directory | |
severity_level (str): Minimum severity level - 'low', 'medium', 'high' | |
confidence_level (str): Minimum confidence level - 'low', 'medium', 'high' | |
output_format (str): Output format - 'json', 'txt', 'xml' | |
Returns: | |
Dict: Security analysis results | |
""" | |
try: | |
# Create temporary file or use existing path | |
if scan_type == "code": | |
# Create temporary file with code | |
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp_file: | |
tmp_file.write(code_input) | |
target_path = tmp_file.name | |
else: | |
# Use existing path | |
target_path = code_input | |
if not os.path.exists(target_path): | |
return { | |
"error": f"Path not found: {target_path}", | |
"success": False | |
} | |
# Build bandit command | |
cmd = ["bandit"] | |
# Add severity level flags | |
if severity_level == "medium": | |
cmd.append("-ll") | |
elif severity_level == "high": | |
cmd.append("-lll") | |
# Add confidence level flags | |
if confidence_level == "medium": | |
cmd.append("-ii") | |
elif confidence_level == "high": | |
cmd.append("-iii") | |
# Add output format | |
if output_format == "json": | |
cmd.extend(["-f", "json"]) | |
elif output_format == "xml": | |
cmd.extend(["-f", "xml"]) | |
# Add recursive scanning for directories | |
if scan_type == "path" and os.path.isdir(target_path): | |
cmd.append("-r") | |
# Add scan target path | |
cmd.append(target_path) | |
# Execute command | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
# Remove temporary file if created | |
if scan_type == "code": | |
try: | |
os.unlink(target_path) | |
except: | |
pass | |
# Process result | |
if output_format == "json": | |
try: | |
output_data = json.loads(result.stdout) if result.stdout else {} | |
return { | |
"success": True, | |
"results": output_data, | |
"stderr": result.stderr, | |
"return_code": result.returncode | |
} | |
except json.JSONDecodeError: | |
return { | |
"success": False, | |
"error": "JSON parsing error", | |
"stdout": result.stdout, | |
"stderr": result.stderr, | |
"return_code": result.returncode | |
} | |
else: | |
return { | |
"success": True, | |
"output": result.stdout, | |
"stderr": result.stderr, | |
"return_code": result.returncode | |
} | |
except Exception as e: | |
return { | |
"success": False, | |
"error": f"Error executing Bandit: {str(e)}" | |
} | |
def bandit_baseline( | |
target_path: str, | |
baseline_file: str | |
) -> Dict: | |
""" | |
Creates baseline file for Bandit or compares with existing baseline. | |
Args: | |
target_path (str): Path to code for analysis | |
baseline_file (str): Path to baseline file | |
Returns: | |
Dict: Result of baseline creation or comparison | |
""" | |
try: | |
if not os.path.exists(target_path): | |
return { | |
"error": f"Path not found: {target_path}", | |
"success": False | |
} | |
# If baseline file doesn't exist, create it | |
if not os.path.exists(baseline_file): | |
cmd = ["bandit", "-r", target_path, "-f", "json", "-o", baseline_file] | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
return { | |
"success": True, | |
"action": "created", | |
"message": f"Baseline file created: {baseline_file}", | |
"return_code": result.returncode, | |
"stderr": result.stderr | |
} | |
else: | |
# Compare with existing baseline | |
cmd = ["bandit", "-r", target_path, "-b", baseline_file, "-f", "json"] | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
try: | |
output_data = json.loads(result.stdout) if result.stdout else {} | |
return { | |
"success": True, | |
"action": "compared", | |
"results": output_data, | |
"return_code": result.returncode, | |
"stderr": result.stderr | |
} | |
except json.JSONDecodeError: | |
return { | |
"success": False, | |
"error": "JSON parsing error when comparing with baseline", | |
"stdout": result.stdout, | |
"stderr": result.stderr | |
} | |
except Exception as e: | |
return { | |
"success": False, | |
"error": f"Error working with baseline: {str(e)}" | |
} | |
def bandit_profile_scan( | |
target_path: str, | |
profile_name: str = "ShellInjection" | |
) -> Dict: | |
""" | |
Runs Bandit with a specific security profile. | |
Args: | |
target_path (str): Path to code for analysis | |
profile_name (str): Profile name (e.g., 'ShellInjection') | |
Returns: | |
Dict: Analysis results using the profile | |
""" | |
try: | |
if not os.path.exists(target_path): | |
return { | |
"error": f"Path not found: {target_path}", | |
"success": False | |
} | |
cmd = ["bandit", "-p", profile_name, "-f", "json"] | |
if os.path.isdir(target_path): | |
cmd.extend(["-r", target_path]) | |
else: | |
cmd.append(target_path) | |
result = subprocess.run(cmd, capture_output=True, text=True) | |
try: | |
output_data = json.loads(result.stdout) if result.stdout else {} | |
return { | |
"success": True, | |
"profile": profile_name, | |
"results": output_data, | |
"return_code": result.returncode, | |
"stderr": result.stderr | |
} | |
except json.JSONDecodeError: | |
return { | |
"success": False, | |
"error": "JSON parsing error", | |
"stdout": result.stdout, | |
"stderr": result.stderr | |
} | |
except Exception as e: | |
return { | |
"success": False, | |
"error": f"Error executing profile scan: {str(e)}" | |
} | |
# Create Gradio interfaces | |
with gr.Blocks(title="Bandit Security Scanner MCP") as demo: | |
gr.Markdown("# 🔒 Bandit Security Scanner") | |
gr.Markdown("Python code security analyzer with MCP support") | |
with gr.Tab("Basic Scanning"): | |
with gr.Row(): | |
with gr.Column(): | |
scan_type = gr.Radio( | |
choices=["code", "path"], | |
value="code", | |
label="Scan Type" | |
) | |
code_input = gr.Textbox( | |
lines=10, | |
placeholder="Enter Python code or path to file/directory...", | |
label="Code or Path" | |
) | |
severity = gr.Dropdown( | |
choices=["low", "medium", "high"], | |
value="low", | |
label="Minimum Severity Level" | |
) | |
confidence = gr.Dropdown( | |
choices=["low", "medium", "high"], | |
value="low", | |
label="Minimum Confidence Level" | |
) | |
output_format = gr.Dropdown( | |
choices=["json", "txt"], | |
value="json", | |
label="Output Format" | |
) | |
scan_btn = gr.Button("🔍 Scan", variant="primary") | |
with gr.Column(): | |
scan_output = gr.JSON(label="Scan Results") | |
scan_btn.click( | |
fn=bandit_scan, | |
inputs=[code_input, scan_type, severity, confidence, output_format], | |
outputs=scan_output | |
) | |
with gr.Tab("Baseline Management"): | |
with gr.Row(): | |
with gr.Column(): | |
baseline_path = gr.Textbox( | |
label="Project Path", | |
placeholder="/path/to/your/project" | |
) | |
baseline_file = gr.Textbox( | |
label="Baseline File Path", | |
placeholder="/path/to/baseline.json" | |
) | |
baseline_btn = gr.Button("📋 Create/Compare Baseline", variant="secondary") | |
with gr.Column(): | |
baseline_output = gr.JSON(label="Baseline Results") | |
baseline_btn.click( | |
fn=bandit_baseline, | |
inputs=[baseline_path, baseline_file], | |
outputs=baseline_output | |
) | |
with gr.Tab("Profile Scanning"): | |
with gr.Row(): | |
with gr.Column(): | |
profile_path = gr.Textbox( | |
label="Project Path", | |
placeholder="/path/to/your/project" | |
) | |
profile_name = gr.Dropdown( | |
choices=["ShellInjection", "SqlInjection", "Crypto", "Subprocess"], | |
value="ShellInjection", | |
label="Security Profile" | |
) | |
profile_btn = gr.Button("🎯 Scan with Profile", variant="secondary") | |
with gr.Column(): | |
profile_output = gr.JSON(label="Profile Scan Results") | |
profile_btn.click( | |
fn=bandit_profile_scan, | |
inputs=[profile_path, profile_name], | |
outputs=profile_output | |
) | |
with gr.Tab("Examples"): | |
gr.Markdown(""" | |
## 🚨 Vulnerable code examples for testing: | |
### 1. Using eval() | |
```python | |
user_input = "print('hello')" | |
eval(user_input) # B307: Use of possibly insecure function | |
``` | |
### 2. Hardcoded password | |
```python | |
password = "secret123" # B105: Possible hardcoded password | |
``` | |
### 3. Insecure subprocess | |
```python | |
import subprocess | |
subprocess.call("ls -la", shell=True) # B602: subprocess call with shell=True | |
``` | |
### 4. Using pickle | |
```python | |
import pickle | |
data = pickle.loads(user_data) # B301: Pickle usage | |
``` | |
""") | |
if __name__ == "__main__": | |
# Получаем настройки сервера из переменных окружения | |
server_name = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0") | |
server_port = int(os.getenv("GRADIO_SERVER_PORT", "7861")) | |
demo.launch( | |
mcp_server=True, | |
server_name=server_name, | |
server_port=server_port, | |
share=False | |
) | |