Spaces:
Running
Running
Upload 21 files
Browse files- DOCKER_SETUP.md +127 -0
- README_2.md +513 -0
- agent.json +16 -0
- agent_requirements.txt +134 -0
- bandit_mcp.py +347 -0
- circle_test_mcp.py +154 -0
- detect_secrets_mcp.py +479 -0
- docker-compose.yml +157 -0
- docker/agent.Dockerfile +42 -0
- docker/bandit.Dockerfile +42 -0
- docker/circle_test.Dockerfile +42 -0
- docker/detect_secrets.Dockerfile +42 -0
- docker/pip_audit.Dockerfile +42 -0
- docker/semgrep.Dockerfile +42 -0
- main.py +571 -0
- mcp.json +54 -0
- pip_audit_mcp.py +79 -0
- requirements.txt +9 -0
- semgrep_mcp.py +210 -0
- test_client.py +115 -0
- test_dependencies.py +126 -0
DOCKER_SETUP.md
ADDED
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# π Docker Setup Π΄Π»Ρ Security Tools MCP
|
2 |
+
|
3 |
+
## π ΠΡΡΡΡΡΠΉ ΡΡΠ°ΡΡ
|
4 |
+
|
5 |
+
### 1. Π‘ΠΎΠ·Π΄Π°ΠΉΡΠ΅ ΡΠ°ΠΉΠ» `.env`:
|
6 |
+
```bash
|
7 |
+
# Π‘ΠΎΠ·Π΄Π°ΠΉΡΠ΅ .env Π² ΠΊΠΎΡΠ½Π΅ ΠΏΡΠΎΠ΅ΠΊΡΠ°
|
8 |
+
touch .env
|
9 |
+
```
|
10 |
+
|
11 |
+
### 2. ΠΠ°ΠΏΠΎΠ»Π½ΠΈΡΠ΅ `.env` ΡΠ°ΠΉΠ»:
|
12 |
+
```bash
|
13 |
+
# ======================
|
14 |
+
# API KEYS
|
15 |
+
# ======================
|
16 |
+
NEBIUS_API_KEY=your_api_key_here
|
17 |
+
CIRCLE_API_URL=https://api.example.com/protect/check_violation
|
18 |
+
|
19 |
+
# ======================
|
20 |
+
# SERVER CONFIGURATION
|
21 |
+
# ======================
|
22 |
+
GRADIO_SERVER_NAME=0.0.0.0
|
23 |
+
|
24 |
+
# ======================
|
25 |
+
# MAIN AGENT PORTS
|
26 |
+
# ======================
|
27 |
+
AGENT_EXTERNAL_PORT=7860
|
28 |
+
AGENT_INTERNAL_PORT=7860
|
29 |
+
|
30 |
+
# ======================
|
31 |
+
# MCP SERVERS PORTS
|
32 |
+
# ======================
|
33 |
+
|
34 |
+
# Bandit Security Scanner
|
35 |
+
BANDIT_EXTERNAL_PORT=7861
|
36 |
+
BANDIT_INTERNAL_PORT=7861
|
37 |
+
|
38 |
+
# Detect Secrets Scanner
|
39 |
+
DETECT_SECRETS_EXTERNAL_PORT=7862
|
40 |
+
DETECT_SECRETS_INTERNAL_PORT=7862
|
41 |
+
|
42 |
+
# Pip Audit Scanner
|
43 |
+
PIP_AUDIT_EXTERNAL_PORT=7863
|
44 |
+
PIP_AUDIT_INTERNAL_PORT=7863
|
45 |
+
|
46 |
+
# Circle Test Scanner
|
47 |
+
CIRCLE_TEST_EXTERNAL_PORT=7864
|
48 |
+
CIRCLE_TEST_INTERNAL_PORT=7864
|
49 |
+
|
50 |
+
# Semgrep Scanner
|
51 |
+
SEMGREP_EXTERNAL_PORT=7865
|
52 |
+
SEMGREP_INTERNAL_PORT=7865
|
53 |
+
```
|
54 |
+
|
55 |
+
### 3. ΠΠ°ΠΏΡΡΠΊ:
|
56 |
+
```bash
|
57 |
+
# ΠΠ°ΠΏΡΡΠΊ Π²ΡΠ΅Ρ
ΡΠ΅ΡΠ²ΠΈΡΠΎΠ²
|
58 |
+
docker-compose up --build
|
59 |
+
|
60 |
+
# ΠΠ°ΠΏΡΡΠΊ Π² ΡΠΎΠ½Π΅
|
61 |
+
docker-compose up -d
|
62 |
+
|
63 |
+
# Π’ΠΎΠ»ΡΠΊΠΎ Π³Π»Π°Π²Π½ΡΠΉ Π°Π³Π΅Π½Ρ + MCP ΡΠ΅ΡΠ²Π΅ΡΡ
|
64 |
+
docker-compose up security-tools-agent
|
65 |
+
```
|
66 |
+
|
67 |
+
## π ΠΠΎΡΡΡΠΏ ΠΊ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡΠΌ:
|
68 |
+
|
69 |
+
- **π― Main Agent**: http://localhost:7860 (ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅)
|
70 |
+
- **π Bandit**: http://localhost:7861
|
71 |
+
- **π Detect Secrets**: http://localhost:7862
|
72 |
+
- **π‘οΈ Pip Audit**: http://localhost:7863
|
73 |
+
- **π Circle Test**: http://localhost:7864
|
74 |
+
- **π Semgrep**: http://localhost:7865
|
75 |
+
|
76 |
+
## βοΈ ΠΠ°ΡΡΠΎΠΌΠΈΠ·Π°ΡΠΈΡ ΠΏΠΎΡΡΠΎΠ²:
|
77 |
+
|
78 |
+
ΠΡΠ»ΠΈ ΠΏΠΎΡΡΡ Π·Π°Π½ΡΡΡ, ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΠ΅ Π² `.env`:
|
79 |
+
```bash
|
80 |
+
# ΠΠ»ΡΡΠ΅ΡΠ½Π°ΡΠΈΠ²Π½ΡΠ΅ ΠΏΠΎΡΡΡ
|
81 |
+
AGENT_EXTERNAL_PORT=8060
|
82 |
+
BANDIT_EXTERNAL_PORT=8061
|
83 |
+
DETECT_SECRETS_EXTERNAL_PORT=8062
|
84 |
+
PIP_AUDIT_EXTERNAL_PORT=8063
|
85 |
+
CIRCLE_TEST_EXTERNAL_PORT=8064
|
86 |
+
SEMGREP_EXTERNAL_PORT=8065
|
87 |
+
```
|
88 |
+
|
89 |
+
## π§ ΠΠΎΠ»Π΅Π·Π½ΡΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ:
|
90 |
+
|
91 |
+
```bash
|
92 |
+
# Π‘ΡΠ°ΡΡΡ ΡΠ΅ΡΠ²ΠΈΡΠΎΠ²
|
93 |
+
docker-compose ps
|
94 |
+
|
95 |
+
# ΠΠΎΠ³ΠΈ Π³Π»Π°Π²Π½ΠΎΠ³ΠΎ Π°Π³Π΅Π½ΡΠ°
|
96 |
+
docker-compose logs security-tools-agent
|
97 |
+
|
98 |
+
# ΠΠΎΠ³ΠΈ Π²ΡΠ΅Ρ
ΡΠ΅ΡΠ²ΠΈΡΠΎΠ²
|
99 |
+
docker-compose logs -f
|
100 |
+
|
101 |
+
# ΠΡΡΠ°Π½ΠΎΠ²ΠΊΠ°
|
102 |
+
docker-compose down
|
103 |
+
|
104 |
+
# ΠΠΎΠ»Π½Π°Ρ ΠΎΡΠΈΡΡΠΊΠ°
|
105 |
+
docker-compose down -v --rmi all
|
106 |
+
```
|
107 |
+
|
108 |
+
## ποΈ ΠΡΡ
ΠΈΡΠ΅ΠΊΡΡΡΠ°:
|
109 |
+
|
110 |
+
```
|
111 |
+
βββββββββββββββββββββββββββββββββββββββββββ
|
112 |
+
β Security Tools Agent β
|
113 |
+
β (main.py) β
|
114 |
+
β Port: 7860 β
|
115 |
+
βββββββββββββββββββ¬ββββββββββββββββββββββββ
|
116 |
+
β
|
117 |
+
βββββββββββββββΌββββββββββββββ
|
118 |
+
β β β
|
119 |
+
βΌ βΌ βΌ
|
120 |
+
βββββββββββ βββββββββββ βββββββββββ
|
121 |
+
β Bandit β βDetect β β ... β
|
122 |
+
β :7861 β βSecrets β β β
|
123 |
+
βββββββββββ β :7862 β βββββββββββ
|
124 |
+
βββββββββββ
|
125 |
+
```
|
126 |
+
|
127 |
+
ΠΡΠ΅ MCP ΡΠ΅ΡΠ²Π΅ΡΡ ΡΠ°Π±ΠΎΡΠ°ΡΡ Π² Docker ΡΠ΅ΡΠΈ `mcp-network` ΠΈ ΠΎΠ±ΡΠ°ΡΡΡΡ ΡΠ΅ΡΠ΅Π· ΠΈΠΌΠ΅Π½Π° ΡΠ΅ΡΠ²ΠΈΡΠΎΠ²!
|
README_2.md
ADDED
@@ -0,0 +1,513 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# π Security Tools MCP Collection
|
2 |
+
|
3 |
+
ΠΠΎΠ»Π»Π΅ΠΊΡΠΈΡ MCP (Model Context Protocol) ΠΎΠ±Π΅ΡΡΠΎΠΊ Π΄Π»Ρ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠΎΠ² Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΠΈ.
|
4 |
+
|
5 |
+
## π Features
|
6 |
+
|
7 |
+
- **Python Code Security Analysis**: Vulnerability detection through AST analysis
|
8 |
+
- **MCP Support**: Integration with any MCP clients
|
9 |
+
- **Web Interface**: Convenient Gradio interface for manual testing
|
10 |
+
- **Baseline Management**: Create and compare with baseline files
|
11 |
+
- **Profile Scanning**: Use specialized security profiles
|
12 |
+
- **Flexible Configuration**: Customize severity and confidence levels
|
13 |
+
- **Dependency Scanning**: Scan Python environments for known vulnerabilities with pip-audit
|
14 |
+
- **Policy Compliance**: Check code against security policies with Circle Test
|
15 |
+
- **Static Analysis**: Advanced code analysis with Semgrep
|
16 |
+
|
17 |
+
## π Quick Start
|
18 |
+
|
19 |
+
### 1. Install Dependencies
|
20 |
+
|
21 |
+
```bash
|
22 |
+
pip install -r requirements.txt
|
23 |
+
```
|
24 |
+
|
25 |
+
### 2. Run Servers
|
26 |
+
|
27 |
+
```bash
|
28 |
+
# Run Bandit MCP server
|
29 |
+
python app.py
|
30 |
+
|
31 |
+
# Run Detect Secrets MCP server
|
32 |
+
python detect_secrets_mcp.py
|
33 |
+
|
34 |
+
# Run Pip Audit MCP server
|
35 |
+
python pip_audit_mcp.py
|
36 |
+
|
37 |
+
# Run Circle Test MCP server
|
38 |
+
python circle_test_mcp.py
|
39 |
+
|
40 |
+
# Run Semgrep MCP server
|
41 |
+
python semgrep_mcp.py
|
42 |
+
```
|
43 |
+
|
44 |
+
The servers will be available at:
|
45 |
+
- **Bandit Web Interface**: `http://localhost:7860`
|
46 |
+
- **Bandit MCP Server**: `http://localhost:7860/gradio_api/mcp/sse`
|
47 |
+
- **Bandit MCP Schema**: `http://localhost:7860/gradio_api/mcp/schema`
|
48 |
+
- **Detect Secrets Web Interface**: `http://localhost:7861`
|
49 |
+
- **Detect Secrets MCP Server**: `http://localhost:7861/gradio_api/mcp/sse`
|
50 |
+
- **Detect Secrets MCP Schema**: `http://localhost:7861/gradio_api/mcp/schema`
|
51 |
+
- **Pip Audit Web Interface**: `http://localhost:7862`
|
52 |
+
- **Pip Audit MCP Server**: `http://localhost:7862/gradio_api/mcp/sse`
|
53 |
+
- **Pip Audit MCP Schema**: `http://localhost:7862/gradio_api/mcp/schema`
|
54 |
+
- **Circle Test Web Interface**: `http://localhost:7863`
|
55 |
+
- **Circle Test MCP Server**: `http://localhost:7863/gradio_api/mcp/sse`
|
56 |
+
- **Circle Test MCP Schema**: `http://localhost:7863/gradio_api/mcp/schema`
|
57 |
+
- **Semgrep Web Interface**: `http://localhost:7864`
|
58 |
+
- **Semgrep MCP Server**: `http://localhost:7864/gradio_api/mcp/sse`
|
59 |
+
- **Semgrep MCP Schema**: `http://localhost:7864/gradio_api/mcp/schema`
|
60 |
+
|
61 |
+
## π§ Available Tools
|
62 |
+
|
63 |
+
### 1. Bandit Tools
|
64 |
+
|
65 |
+
#### 1.1 `bandit_scan` - Basic Scanning
|
66 |
+
|
67 |
+
Analyzes Python code for security issues.
|
68 |
+
|
69 |
+
**Parameters:**
|
70 |
+
- `code_input`: Python code or path to file/directory
|
71 |
+
- `scan_type`: "code" (direct code) or "path" (file/directory)
|
72 |
+
- `severity_level`: "low", "medium", "high"
|
73 |
+
- `confidence_level`: "low", "medium", "high"
|
74 |
+
- `output_format`: "json", "txt"
|
75 |
+
|
76 |
+
**Usage Example:**
|
77 |
+
```python
|
78 |
+
bandit_scan(
|
79 |
+
code_input="eval(user_input)",
|
80 |
+
scan_type="code",
|
81 |
+
severity_level="medium",
|
82 |
+
confidence_level="high"
|
83 |
+
)
|
84 |
+
```
|
85 |
+
|
86 |
+
#### 1.2 `bandit_baseline` - Baseline Management
|
87 |
+
|
88 |
+
Creates baseline file or compares with existing one.
|
89 |
+
|
90 |
+
**Parameters:**
|
91 |
+
- `target_path`: Path to project for analysis
|
92 |
+
- `baseline_file`: Path to baseline file
|
93 |
+
|
94 |
+
#### 1.3 `bandit_profile_scan` - Profile Scanning
|
95 |
+
|
96 |
+
Runs scanning using specific security profile.
|
97 |
+
|
98 |
+
**Parameters:**
|
99 |
+
- `target_path`: Path to project
|
100 |
+
- `profile_name`: "ShellInjection", "SqlInjection", "Crypto", "Subprocess"
|
101 |
+
|
102 |
+
### 2. Detect Secrets Tools
|
103 |
+
|
104 |
+
#### 2.1 `detect_secrets_scan` - Basic Scanning
|
105 |
+
|
106 |
+
Scans code for secrets using detect-secrets.
|
107 |
+
|
108 |
+
**Parameters:**
|
109 |
+
- `code_input`: Code to scan or path to file/directory
|
110 |
+
- `scan_type`: "code" (direct code) or "path" (file/directory)
|
111 |
+
- `base64_limit`: Entropy limit for base64 strings (0.0-8.0)
|
112 |
+
- `hex_limit`: Entropy limit for hex strings (0.0-8.0)
|
113 |
+
- `exclude_lines`: Regex pattern for lines to exclude
|
114 |
+
- `exclude_files`: Regex pattern for files to exclude
|
115 |
+
- `exclude_secrets`: Regex pattern for secrets to exclude
|
116 |
+
- `word_list`: Path to word list file
|
117 |
+
- `output_format`: "json" or "txt"
|
118 |
+
|
119 |
+
**Usage Example:**
|
120 |
+
```python
|
121 |
+
detect_secrets_scan(
|
122 |
+
code_input="API_KEY = 'sk_live_51H1h2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8'",
|
123 |
+
scan_type="code",
|
124 |
+
base64_limit=4.5,
|
125 |
+
hex_limit=3.0
|
126 |
+
)
|
127 |
+
```
|
128 |
+
|
129 |
+
#### 2.2 `detect_secrets_baseline` - Baseline Management
|
130 |
+
|
131 |
+
Creates or updates a baseline file for detect-secrets.
|
132 |
+
|
133 |
+
**Parameters:**
|
134 |
+
- `target_path`: Path to code for analysis
|
135 |
+
- `baseline_file`: Path to baseline file
|
136 |
+
- `base64_limit`: Entropy limit for base64 strings
|
137 |
+
- `hex_limit`: Entropy limit for hex strings
|
138 |
+
|
139 |
+
#### 2.3 `detect_secrets_audit` - Baseline Audit
|
140 |
+
|
141 |
+
Audits a detect-secrets baseline file.
|
142 |
+
|
143 |
+
**Parameters:**
|
144 |
+
- `baseline_file`: Path to baseline file
|
145 |
+
- `show_stats`: Show statistics
|
146 |
+
- `show_report`: Show report
|
147 |
+
- `only_real`: Only show real secrets
|
148 |
+
- `only_false`: Only show false positives
|
149 |
+
|
150 |
+
### 3. Pip Audit Tools
|
151 |
+
|
152 |
+
#### 3.1 `pip_audit_scan` - Basic Scanning
|
153 |
+
|
154 |
+
Scans Python environment for known vulnerabilities using pip-audit.
|
155 |
+
|
156 |
+
**Parameters:**
|
157 |
+
- No parameters required - scans current Python environment
|
158 |
+
|
159 |
+
**Usage Example:**
|
160 |
+
```python
|
161 |
+
pip_audit_scan()
|
162 |
+
```
|
163 |
+
|
164 |
+
**Example Output:**
|
165 |
+
```json
|
166 |
+
{
|
167 |
+
"success": true,
|
168 |
+
"results": {
|
169 |
+
"vulnerabilities": [
|
170 |
+
{
|
171 |
+
"name": "package-name",
|
172 |
+
"installed_version": "1.0.0",
|
173 |
+
"fixed_version": "1.0.1",
|
174 |
+
"description": "Vulnerability description",
|
175 |
+
"aliases": ["CVE-2024-XXXX"]
|
176 |
+
}
|
177 |
+
]
|
178 |
+
}
|
179 |
+
}
|
180 |
+
```
|
181 |
+
|
182 |
+
### 4. Circle Test Tools
|
183 |
+
|
184 |
+
#### 4.1 `check_violation` - Policy Compliance Check
|
185 |
+
|
186 |
+
Checks code against security policies.
|
187 |
+
|
188 |
+
**Parameters:**
|
189 |
+
- `code_input`: Code to check
|
190 |
+
- `policies`: Dictionary of security policies
|
191 |
+
|
192 |
+
**Usage Example:**
|
193 |
+
```python
|
194 |
+
check_violation(
|
195 |
+
code_input="def read_file(filename):\n with open(filename, 'r') as f:\n return f.read()",
|
196 |
+
policies={
|
197 |
+
"1": "Presence of SPDX-License-Identifier...",
|
198 |
+
"2": "Presence of plaintext credentials..."
|
199 |
+
}
|
200 |
+
)
|
201 |
+
```
|
202 |
+
|
203 |
+
**Example Output:**
|
204 |
+
```json
|
205 |
+
{
|
206 |
+
"success": true,
|
207 |
+
"results": {
|
208 |
+
"1": {
|
209 |
+
"policy": "Presence of SPDX-License-Identifier...",
|
210 |
+
"violation": "no"
|
211 |
+
},
|
212 |
+
"2": {
|
213 |
+
"policy": "Presence of plaintext credentials...",
|
214 |
+
"violation": "yes"
|
215 |
+
}
|
216 |
+
}
|
217 |
+
}
|
218 |
+
```
|
219 |
+
|
220 |
+
### 5. Semgrep Tools
|
221 |
+
|
222 |
+
#### 5.1 `semgrep_scan` - Basic Scanning
|
223 |
+
|
224 |
+
Scans code using Semgrep rules.
|
225 |
+
|
226 |
+
**Parameters:**
|
227 |
+
- `code_input`: Code to scan or path to file/directory
|
228 |
+
- `scan_type`: "code" (direct code) or "path" (file/directory)
|
229 |
+
- `rules`: Rules to use (e.g., "p/default" or path to rules file)
|
230 |
+
- `output_format`: "json" or "text"
|
231 |
+
|
232 |
+
**Usage Example:**
|
233 |
+
```python
|
234 |
+
semgrep_scan(
|
235 |
+
code_input="def get_user(user_id):\n query = f'SELECT * FROM users WHERE id = {user_id}'\n return db.execute(query)",
|
236 |
+
scan_type="code",
|
237 |
+
rules="p/default",
|
238 |
+
output_format="json"
|
239 |
+
)
|
240 |
+
```
|
241 |
+
|
242 |
+
#### 5.2 `semgrep_list_rules` - List Available Rules
|
243 |
+
|
244 |
+
Lists available Semgrep rules.
|
245 |
+
|
246 |
+
**Parameters:**
|
247 |
+
- No parameters required
|
248 |
+
|
249 |
+
**Usage Example:**
|
250 |
+
```python
|
251 |
+
semgrep_list_rules()
|
252 |
+
```
|
253 |
+
|
254 |
+
## π― What Bandit Detects
|
255 |
+
|
256 |
+
- **Insecure Functions**: `exec()`, `eval()`, `compile()`
|
257 |
+
- **Hardcoded Passwords**: Hard-coded secrets in code
|
258 |
+
- **Insecure Serialization**: Using `pickle` without validation
|
259 |
+
- **SQL Injections**: Unsafe SQL query formation
|
260 |
+
- **Shell Injections**: Command execution with `shell=True`
|
261 |
+
- **SSL Issues**: Missing certificate verification
|
262 |
+
- **Weak Encryption Algorithms**: Using outdated methods
|
263 |
+
- **File Permission Issues**: Insecure file permissions
|
264 |
+
|
265 |
+
## π What Detect Secrets Detects
|
266 |
+
|
267 |
+
- **API Keys**: Various service API keys
|
268 |
+
- **Passwords**: High entropy strings that look like passwords
|
269 |
+
- **Private Keys**: RSA, SSH, and other private keys
|
270 |
+
- **OAuth Tokens**: Various OAuth tokens
|
271 |
+
- **AWS Keys**: AWS access and secret keys
|
272 |
+
- **GitHub Tokens**: GitHub personal access tokens
|
273 |
+
- **Slack Tokens**: Slack API tokens
|
274 |
+
- **Stripe Keys**: Stripe API keys
|
275 |
+
- **And More**: Many other types of secrets
|
276 |
+
|
277 |
+
## π‘οΈ What Pip Audit Detects
|
278 |
+
|
279 |
+
- **Known Vulnerabilities**: CVE and other security advisories
|
280 |
+
- **Outdated Dependencies**: Packages with known security issues
|
281 |
+
- **Version Conflicts**: Incompatible package versions
|
282 |
+
- **Deprecated Packages**: Packages that are no longer maintained
|
283 |
+
- **Supply Chain Issues**: Compromised or malicious packages
|
284 |
+
|
285 |
+
## π What Circle Test Checks
|
286 |
+
|
287 |
+
- **License Compliance**: SPDX-License-Identifier presence and validity
|
288 |
+
- **Credential Management**: Plaintext credentials in configuration files
|
289 |
+
- **Code Quality**: TODO/FIXME tags in production code
|
290 |
+
- **Security Best Practices**: HTTP usage, logging of sensitive data
|
291 |
+
- **API Usage**: Deprecated API calls
|
292 |
+
- **Input Validation**: Unsanitized user input in commands
|
293 |
+
- **File Operations**: Unsafe file path handling
|
294 |
+
- **Database Security**: SQL injection prevention
|
295 |
+
- **Path Management**: Absolute path usage
|
296 |
+
- **Environment Management**: Production environment references
|
297 |
+
- **Dependency Management**: Version pinning in lock files
|
298 |
+
|
299 |
+
## π What Semgrep Detects
|
300 |
+
|
301 |
+
- **Security Vulnerabilities**: SQL injection, command injection, path traversal
|
302 |
+
- **Code Quality Issues**: Anti-patterns, best practices violations
|
303 |
+
- **Custom Rules**: User-defined security and style rules
|
304 |
+
- **Language-Specific Issues**: Language-specific vulnerabilities
|
305 |
+
- **Framework-Specific Issues**: Framework-specific security concerns
|
306 |
+
|
307 |
+
## π§ͺ Vulnerable Code Examples
|
308 |
+
|
309 |
+
### 1. Using eval()
|
310 |
+
```python
|
311 |
+
user_input = "print('hello')"
|
312 |
+
eval(user_input) # B307: Use of possibly insecure function
|
313 |
+
```
|
314 |
+
|
315 |
+
### 2. Hardcoded password
|
316 |
+
```python
|
317 |
+
password = "secret123" # B105: Possible hardcoded password
|
318 |
+
```
|
319 |
+
|
320 |
+
### 3. Insecure subprocess
|
321 |
+
```python
|
322 |
+
import subprocess
|
323 |
+
subprocess.call("ls -la", shell=True) # B602: subprocess call with shell=True
|
324 |
+
```
|
325 |
+
|
326 |
+
### 4. Using pickle
|
327 |
+
```python
|
328 |
+
import pickle
|
329 |
+
data = pickle.loads(user_data) # B301: Pickle usage
|
330 |
+
```
|
331 |
+
|
332 |
+
### 5. API Key
|
333 |
+
```python
|
334 |
+
API_KEY = "sk_live_51H1h2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8" # Detect Secrets: API Key
|
335 |
+
```
|
336 |
+
|
337 |
+
### 6. Private Key
|
338 |
+
```python
|
339 |
+
private_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA..." # Detect Secrets: Private Key
|
340 |
+
```
|
341 |
+
|
342 |
+
## π MCP Client Integration
|
343 |
+
|
344 |
+
### Configuration for Cursor IDE
|
345 |
+
|
346 |
+
```json
|
347 |
+
{
|
348 |
+
"mcpServers": {
|
349 |
+
"bandit-security": {
|
350 |
+
"command": "npx",
|
351 |
+
"args": [
|
352 |
+
"-y",
|
353 |
+
"mcp-remote",
|
354 |
+
"http://localhost:7860/gradio_api/mcp/sse",
|
355 |
+
"--transport",
|
356 |
+
"sse-only"
|
357 |
+
]
|
358 |
+
},
|
359 |
+
"detect-secrets": {
|
360 |
+
"command": "npx",
|
361 |
+
"args": [
|
362 |
+
"-y",
|
363 |
+
"mcp-remote",
|
364 |
+
"http://localhost:7861/gradio_api/mcp/sse",
|
365 |
+
"--transport",
|
366 |
+
"sse-only"
|
367 |
+
]
|
368 |
+
},
|
369 |
+
"pip-audit": {
|
370 |
+
"command": "npx",
|
371 |
+
"args": [
|
372 |
+
"-y",
|
373 |
+
"mcp-remote",
|
374 |
+
"http://localhost:7862/gradio_api/mcp/sse",
|
375 |
+
"--transport",
|
376 |
+
"sse-only"
|
377 |
+
]
|
378 |
+
},
|
379 |
+
"circle-test": {
|
380 |
+
"command": "npx",
|
381 |
+
"args": [
|
382 |
+
"-y",
|
383 |
+
"mcp-remote",
|
384 |
+
"http://localhost:7863/gradio_api/mcp/sse",
|
385 |
+
"--transport",
|
386 |
+
"sse-only"
|
387 |
+
]
|
388 |
+
},
|
389 |
+
"semgrep": {
|
390 |
+
"command": "npx",
|
391 |
+
"args": [
|
392 |
+
"-y",
|
393 |
+
"mcp-remote",
|
394 |
+
"http://localhost:7864/gradio_api/mcp/sse",
|
395 |
+
"--transport",
|
396 |
+
"sse-only"
|
397 |
+
]
|
398 |
+
}
|
399 |
+
}
|
400 |
+
}
|
401 |
+
```
|
402 |
+
|
403 |
+
### Configuration for Other MCP Clients
|
404 |
+
|
405 |
+
```json
|
406 |
+
{
|
407 |
+
"servers": [
|
408 |
+
{
|
409 |
+
"name": "Bandit Security Scanner",
|
410 |
+
"transport": {
|
411 |
+
"type": "sse",
|
412 |
+
"url": "http://localhost:7860/gradio_api/mcp/sse"
|
413 |
+
}
|
414 |
+
},
|
415 |
+
{
|
416 |
+
"name": "Detect Secrets Scanner",
|
417 |
+
"transport": {
|
418 |
+
"type": "sse",
|
419 |
+
"url": "http://localhost:7861/gradio_api/mcp/sse"
|
420 |
+
}
|
421 |
+
},
|
422 |
+
{
|
423 |
+
"name": "Pip Audit Scanner",
|
424 |
+
"transport": {
|
425 |
+
"type": "sse",
|
426 |
+
"url": "http://localhost:7862/gradio_api/mcp/sse"
|
427 |
+
}
|
428 |
+
},
|
429 |
+
{
|
430 |
+
"name": "Circle Test Scanner",
|
431 |
+
"transport": {
|
432 |
+
"type": "sse",
|
433 |
+
"url": "http://localhost:7863/gradio_api/mcp/sse"
|
434 |
+
}
|
435 |
+
},
|
436 |
+
{
|
437 |
+
"name": "Semgrep Scanner",
|
438 |
+
"transport": {
|
439 |
+
"type": "sse",
|
440 |
+
"url": "http://localhost:7864/gradio_api/mcp/sse"
|
441 |
+
}
|
442 |
+
}
|
443 |
+
]
|
444 |
+
}
|
445 |
+
```
|
446 |
+
|
447 |
+
## π Results Format
|
448 |
+
|
449 |
+
### JSON Scan Result
|
450 |
+
```json
|
451 |
+
{
|
452 |
+
"success": true,
|
453 |
+
"results": {
|
454 |
+
"errors": [],
|
455 |
+
"generated_at": "2024-01-01T12:00:00Z",
|
456 |
+
"metrics": {
|
457 |
+
"_totals": {
|
458 |
+
"CONFIDENCE.HIGH": 1,
|
459 |
+
"SEVERITY.MEDIUM": 1,
|
460 |
+
"loc": 10,
|
461 |
+
"nosec": 0
|
462 |
+
}
|
463 |
+
},
|
464 |
+
"results": [
|
465 |
+
{
|
466 |
+
"code": "eval(user_input)",
|
467 |
+
"filename": "/tmp/example.py",
|
468 |
+
"issue_confidence": "HIGH",
|
469 |
+
"issue_severity": "MEDIUM",
|
470 |
+
"issue_text": "Use of possibly insecure function - consider using safer alternatives.",
|
471 |
+
"line_number": 2,
|
472 |
+
"line_range": [2],
|
473 |
+
"test_id": "B307",
|
474 |
+
"test_name": "blacklist"
|
475 |
+
}
|
476 |
+
]
|
477 |
+
}
|
478 |
+
}
|
479 |
+
```
|
480 |
+
|
481 |
+
## π Deploy on Hugging Face Spaces
|
482 |
+
|
483 |
+
1. Create a new Space on Hugging Face
|
484 |
+
2. Choose Gradio SDK
|
485 |
+
3. Upload `app.py`, `detect_secrets_mcp.py`, `pip_audit_mcp.py`, `circle_test_mcp.py`, `semgrep_mcp.py` and `requirements.txt` files
|
486 |
+
4. MCP servers will be available at:
|
487 |
+
- Bandit: `https://YOUR_USERNAME-bandit-mcp.hf.space/gradio_api/mcp/sse`
|
488 |
+
- Detect Secrets: `https://YOUR_USERNAME-detect-secrets-mcp.hf.space/gradio_api/mcp/sse`
|
489 |
+
- Pip Audit: `https://YOUR_USERNAME-pip-audit-mcp.hf.space/gradio_api/mcp/sse`
|
490 |
+
- Circle Test: `https://YOUR_USERNAME-circle-test-mcp.hf.space/gradio_api/mcp/sse`
|
491 |
+
- Semgrep: `https://YOUR_USERNAME-semgrep-mcp.hf.space/gradio_api/mcp/sse`
|
492 |
+
|
493 |
+
## π€ AI Agent Integration
|
494 |
+
|
495 |
+
This MCP server can be integrated with any AI agents supporting MCP:
|
496 |
+
|
497 |
+
- **Claude Desktop**: Through MCP configuration
|
498 |
+
- **Cursor IDE**: Through MCP server settings
|
499 |
+
- **Tiny Agents**: Through JavaScript or Python clients
|
500 |
+
- **Custom Agents**: Through HTTP+SSE or stdio
|
501 |
+
|
502 |
+
## π Additional Resources
|
503 |
+
|
504 |
+
- [Bandit Documentation](https://bandit.readthedocs.io/)
|
505 |
+
- [Detect Secrets Documentation](https://github.com/Yelp/detect-secrets)
|
506 |
+
- [Pip Audit Documentation](https://pypi.org/project/pip-audit/)
|
507 |
+
- [Semgrep Documentation](https://semgrep.dev/docs/)
|
508 |
+
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
509 |
+
- [Gradio MCP Integration](https://gradio.app/guides/mcp-integration/)
|
510 |
+
|
511 |
+
---
|
512 |
+
|
513 |
+
**Note**: Bandit, Detect Secrets, Pip Audit, Circle Test, and Semgrep are static analyzers and cannot detect all types of vulnerabilities. Use them as part of a comprehensive security strategy.
|
agent.json
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"model": "Qwen/Qwen2.5-72B-Instruct",
|
3 |
+
"provider": "nebius",
|
4 |
+
"servers": [
|
5 |
+
{
|
6 |
+
"type": "stdio",
|
7 |
+
"config": {
|
8 |
+
"command": "npx",
|
9 |
+
"args": [
|
10 |
+
"mcp-remote",
|
11 |
+
"http://localhost:7860/gradio_api/mcp/sse"
|
12 |
+
]
|
13 |
+
}
|
14 |
+
}
|
15 |
+
]
|
16 |
+
}
|
agent_requirements.txt
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
agno==1.5.10
|
2 |
+
aiofiles==24.1.0
|
3 |
+
aiohappyeyeballs==2.6.1
|
4 |
+
aiohttp>=3.8.0
|
5 |
+
aiosignal==1.3.2
|
6 |
+
altair==5.5.0
|
7 |
+
annotated-types==0.7.0
|
8 |
+
attrs==25.3.0
|
9 |
+
bandit[toml,baseline,sarif]>=1.7.0
|
10 |
+
blinker==1.9.0
|
11 |
+
boltons==21.0.0
|
12 |
+
boolean.py==5.0
|
13 |
+
bracex==2.5.post1
|
14 |
+
CacheControl==0.14.3
|
15 |
+
cachetools==5.5.2
|
16 |
+
certifi==2025.4.26
|
17 |
+
charset-normalizer==3.4.2
|
18 |
+
click==8.1.8
|
19 |
+
click-option-group==0.5.7
|
20 |
+
colorama==0.4.6
|
21 |
+
cyclonedx-python-lib>=5,<9
|
22 |
+
defusedxml==0.7.1
|
23 |
+
Deprecated==1.2.18
|
24 |
+
detect-secrets>=1.0.0
|
25 |
+
distro==1.9.0
|
26 |
+
docstring_parser==0.16
|
27 |
+
exceptiongroup==1.2.2
|
28 |
+
face==24.0.0
|
29 |
+
fastapi>=0.100.0
|
30 |
+
ffmpy==0.6.0
|
31 |
+
filelock==3.18.0
|
32 |
+
frozenlist==1.6.2
|
33 |
+
fsspec==2025.5.1
|
34 |
+
gitdb==4.0.12
|
35 |
+
GitPython==3.1.44
|
36 |
+
glom==22.1.0
|
37 |
+
googleapis-common-protos==1.70.0
|
38 |
+
gradio==5.33.0
|
39 |
+
gradio_client==1.10.2
|
40 |
+
groovy==0.1.2
|
41 |
+
h11==0.16.0
|
42 |
+
hf-xet==1.1.3
|
43 |
+
httpcore==1.0.9
|
44 |
+
httpx==0.28.1
|
45 |
+
httpx-sse==0.4.0
|
46 |
+
huggingface-hub==0.32.4
|
47 |
+
idna==3.10
|
48 |
+
importlib_metadata==7.1.0
|
49 |
+
Jinja2==3.1.6
|
50 |
+
jiter==0.10.0
|
51 |
+
jsonschema==4.24.0
|
52 |
+
jsonschema-specifications==2025.4.1
|
53 |
+
license-expression==30.4.1
|
54 |
+
markdown-it-py==3.0.0
|
55 |
+
MarkupSafe==3.0.2
|
56 |
+
mcp>=1.0.0
|
57 |
+
mdurl==0.1.2
|
58 |
+
msgpack==1.1.0
|
59 |
+
multidict==6.4.4
|
60 |
+
narwhals==1.41.1
|
61 |
+
numpy==2.2.6
|
62 |
+
openai==1.84.0
|
63 |
+
opentelemetry-api==1.25.0
|
64 |
+
opentelemetry-exporter-otlp-proto-common==1.25.0
|
65 |
+
opentelemetry-exporter-otlp-proto-http==1.25.0
|
66 |
+
opentelemetry-instrumentation==0.46b0
|
67 |
+
opentelemetry-instrumentation-requests==0.46b0
|
68 |
+
opentelemetry-proto==1.25.0
|
69 |
+
opentelemetry-sdk==1.25.0
|
70 |
+
opentelemetry-semantic-conventions==0.46b0
|
71 |
+
opentelemetry-util-http==0.46b0
|
72 |
+
orjson==3.10.18
|
73 |
+
packageurl-python==0.17.1
|
74 |
+
packaging>=20.9,<25
|
75 |
+
pandas==2.3.0
|
76 |
+
pbr==6.1.1
|
77 |
+
peewee==3.18.1
|
78 |
+
pillow==11.2.1
|
79 |
+
pip-api==0.0.34
|
80 |
+
pip-requirements-parser==32.0.1
|
81 |
+
pip-audit>=2.0.0
|
82 |
+
platformdirs==4.3.8
|
83 |
+
propcache==0.3.1
|
84 |
+
protobuf==4.25.8
|
85 |
+
py-serializable>=1.1.1,<2.0.0
|
86 |
+
pyarrow==20.0.0
|
87 |
+
pydantic==2.11.5
|
88 |
+
pydantic-settings==2.9.1
|
89 |
+
pydantic_core==2.33.2
|
90 |
+
pydeck==0.9.1
|
91 |
+
pydub==0.25.1
|
92 |
+
Pygments==2.19.1
|
93 |
+
pyparsing==3.2.3
|
94 |
+
python-dateutil==2.9.0.post0
|
95 |
+
python-dotenv>=0.19.0
|
96 |
+
python-multipart==0.0.20
|
97 |
+
pytz==2025.2
|
98 |
+
PyYAML==6.0.2
|
99 |
+
referencing==0.36.2
|
100 |
+
requests==2.32.3
|
101 |
+
rich==13.5.3
|
102 |
+
rpds-py==0.25.1
|
103 |
+
ruamel.yaml==0.18.13
|
104 |
+
ruamel.yaml.clib==0.2.12
|
105 |
+
ruff==0.11.13
|
106 |
+
safehttpx==0.1.6
|
107 |
+
semantic-version==2.10.0
|
108 |
+
semgrep==1.124.0
|
109 |
+
shellingham==1.5.4
|
110 |
+
six==1.17.0
|
111 |
+
smmap==5.0.2
|
112 |
+
sniffio==1.3.1
|
113 |
+
sortedcontainers==2.4.0
|
114 |
+
sse-starlette==2.3.6
|
115 |
+
starlette>=0.27.0
|
116 |
+
stevedore==5.4.1
|
117 |
+
streamlit==1.45.1
|
118 |
+
tenacity==9.1.2
|
119 |
+
toml==0.10.2
|
120 |
+
tomli==2.0.2
|
121 |
+
tomlkit==0.13.3
|
122 |
+
tornado==6.5.1
|
123 |
+
tqdm==4.67.1
|
124 |
+
typer==0.16.0
|
125 |
+
typing-inspection==0.4.1
|
126 |
+
typing_extensions==4.14.0
|
127 |
+
tzdata==2025.2
|
128 |
+
urllib3==2.4.0
|
129 |
+
uvicorn>=0.23.0
|
130 |
+
wcmatch==8.5.2
|
131 |
+
websockets==15.0.1
|
132 |
+
wrapt==1.17.2
|
133 |
+
yarl==1.20.0
|
134 |
+
zipp==3.22.0
|
bandit_mcp.py
ADDED
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import subprocess
|
3 |
+
import json
|
4 |
+
import os
|
5 |
+
import tempfile
|
6 |
+
from typing import Dict, List, Optional
|
7 |
+
from pathlib import Path
|
8 |
+
|
9 |
+
def bandit_scan(
|
10 |
+
code_input: str,
|
11 |
+
scan_type: str = "code",
|
12 |
+
severity_level: str = "low",
|
13 |
+
confidence_level: str = "low",
|
14 |
+
output_format: str = "json"
|
15 |
+
) -> Dict:
|
16 |
+
"""
|
17 |
+
Analyzes Python code for security issues using Bandit.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
code_input (str): Python code for analysis or path to file/directory
|
21 |
+
scan_type (str): Scan type - 'code' for direct code or 'path' for file/directory
|
22 |
+
severity_level (str): Minimum severity level - 'low', 'medium', 'high'
|
23 |
+
confidence_level (str): Minimum confidence level - 'low', 'medium', 'high'
|
24 |
+
output_format (str): Output format - 'json', 'txt', 'xml'
|
25 |
+
|
26 |
+
Returns:
|
27 |
+
Dict: Security analysis results
|
28 |
+
"""
|
29 |
+
try:
|
30 |
+
# Create temporary file or use existing path
|
31 |
+
if scan_type == "code":
|
32 |
+
# Create temporary file with code
|
33 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp_file:
|
34 |
+
tmp_file.write(code_input)
|
35 |
+
target_path = tmp_file.name
|
36 |
+
else:
|
37 |
+
# Use existing path
|
38 |
+
target_path = code_input
|
39 |
+
if not os.path.exists(target_path):
|
40 |
+
return {
|
41 |
+
"error": f"Path not found: {target_path}",
|
42 |
+
"success": False
|
43 |
+
}
|
44 |
+
|
45 |
+
# Build bandit command
|
46 |
+
cmd = ["bandit"]
|
47 |
+
|
48 |
+
# Add severity level flags
|
49 |
+
if severity_level == "medium":
|
50 |
+
cmd.append("-ll")
|
51 |
+
elif severity_level == "high":
|
52 |
+
cmd.append("-lll")
|
53 |
+
|
54 |
+
# Add confidence level flags
|
55 |
+
if confidence_level == "medium":
|
56 |
+
cmd.append("-ii")
|
57 |
+
elif confidence_level == "high":
|
58 |
+
cmd.append("-iii")
|
59 |
+
|
60 |
+
# Add output format
|
61 |
+
if output_format == "json":
|
62 |
+
cmd.extend(["-f", "json"])
|
63 |
+
elif output_format == "xml":
|
64 |
+
cmd.extend(["-f", "xml"])
|
65 |
+
|
66 |
+
# Add recursive scanning for directories
|
67 |
+
if scan_type == "path" and os.path.isdir(target_path):
|
68 |
+
cmd.append("-r")
|
69 |
+
|
70 |
+
# Add scan target path
|
71 |
+
cmd.append(target_path)
|
72 |
+
|
73 |
+
# Execute command
|
74 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
75 |
+
|
76 |
+
# Remove temporary file if created
|
77 |
+
if scan_type == "code":
|
78 |
+
try:
|
79 |
+
os.unlink(target_path)
|
80 |
+
except:
|
81 |
+
pass
|
82 |
+
|
83 |
+
# Process result
|
84 |
+
if output_format == "json":
|
85 |
+
try:
|
86 |
+
output_data = json.loads(result.stdout) if result.stdout else {}
|
87 |
+
return {
|
88 |
+
"success": True,
|
89 |
+
"results": output_data,
|
90 |
+
"stderr": result.stderr,
|
91 |
+
"return_code": result.returncode
|
92 |
+
}
|
93 |
+
except json.JSONDecodeError:
|
94 |
+
return {
|
95 |
+
"success": False,
|
96 |
+
"error": "JSON parsing error",
|
97 |
+
"stdout": result.stdout,
|
98 |
+
"stderr": result.stderr,
|
99 |
+
"return_code": result.returncode
|
100 |
+
}
|
101 |
+
else:
|
102 |
+
return {
|
103 |
+
"success": True,
|
104 |
+
"output": result.stdout,
|
105 |
+
"stderr": result.stderr,
|
106 |
+
"return_code": result.returncode
|
107 |
+
}
|
108 |
+
|
109 |
+
except Exception as e:
|
110 |
+
return {
|
111 |
+
"success": False,
|
112 |
+
"error": f"Error executing Bandit: {str(e)}"
|
113 |
+
}
|
114 |
+
|
115 |
+
def bandit_baseline(
|
116 |
+
target_path: str,
|
117 |
+
baseline_file: str
|
118 |
+
) -> Dict:
|
119 |
+
"""
|
120 |
+
Creates baseline file for Bandit or compares with existing baseline.
|
121 |
+
|
122 |
+
Args:
|
123 |
+
target_path (str): Path to code for analysis
|
124 |
+
baseline_file (str): Path to baseline file
|
125 |
+
|
126 |
+
Returns:
|
127 |
+
Dict: Result of baseline creation or comparison
|
128 |
+
"""
|
129 |
+
try:
|
130 |
+
if not os.path.exists(target_path):
|
131 |
+
return {
|
132 |
+
"error": f"Path not found: {target_path}",
|
133 |
+
"success": False
|
134 |
+
}
|
135 |
+
|
136 |
+
# If baseline file doesn't exist, create it
|
137 |
+
if not os.path.exists(baseline_file):
|
138 |
+
cmd = ["bandit", "-r", target_path, "-f", "json", "-o", baseline_file]
|
139 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
140 |
+
|
141 |
+
return {
|
142 |
+
"success": True,
|
143 |
+
"action": "created",
|
144 |
+
"message": f"Baseline file created: {baseline_file}",
|
145 |
+
"return_code": result.returncode,
|
146 |
+
"stderr": result.stderr
|
147 |
+
}
|
148 |
+
else:
|
149 |
+
# Compare with existing baseline
|
150 |
+
cmd = ["bandit", "-r", target_path, "-b", baseline_file, "-f", "json"]
|
151 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
152 |
+
|
153 |
+
try:
|
154 |
+
output_data = json.loads(result.stdout) if result.stdout else {}
|
155 |
+
return {
|
156 |
+
"success": True,
|
157 |
+
"action": "compared",
|
158 |
+
"results": output_data,
|
159 |
+
"return_code": result.returncode,
|
160 |
+
"stderr": result.stderr
|
161 |
+
}
|
162 |
+
except json.JSONDecodeError:
|
163 |
+
return {
|
164 |
+
"success": False,
|
165 |
+
"error": "JSON parsing error when comparing with baseline",
|
166 |
+
"stdout": result.stdout,
|
167 |
+
"stderr": result.stderr
|
168 |
+
}
|
169 |
+
|
170 |
+
except Exception as e:
|
171 |
+
return {
|
172 |
+
"success": False,
|
173 |
+
"error": f"Error working with baseline: {str(e)}"
|
174 |
+
}
|
175 |
+
|
176 |
+
def bandit_profile_scan(
|
177 |
+
target_path: str,
|
178 |
+
profile_name: str = "ShellInjection"
|
179 |
+
) -> Dict:
|
180 |
+
"""
|
181 |
+
Runs Bandit with a specific security profile.
|
182 |
+
|
183 |
+
Args:
|
184 |
+
target_path (str): Path to code for analysis
|
185 |
+
profile_name (str): Profile name (e.g., 'ShellInjection')
|
186 |
+
|
187 |
+
Returns:
|
188 |
+
Dict: Analysis results using the profile
|
189 |
+
"""
|
190 |
+
try:
|
191 |
+
if not os.path.exists(target_path):
|
192 |
+
return {
|
193 |
+
"error": f"Path not found: {target_path}",
|
194 |
+
"success": False
|
195 |
+
}
|
196 |
+
|
197 |
+
cmd = ["bandit", "-p", profile_name, "-f", "json"]
|
198 |
+
|
199 |
+
if os.path.isdir(target_path):
|
200 |
+
cmd.extend(["-r", target_path])
|
201 |
+
else:
|
202 |
+
cmd.append(target_path)
|
203 |
+
|
204 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
205 |
+
|
206 |
+
try:
|
207 |
+
output_data = json.loads(result.stdout) if result.stdout else {}
|
208 |
+
return {
|
209 |
+
"success": True,
|
210 |
+
"profile": profile_name,
|
211 |
+
"results": output_data,
|
212 |
+
"return_code": result.returncode,
|
213 |
+
"stderr": result.stderr
|
214 |
+
}
|
215 |
+
except json.JSONDecodeError:
|
216 |
+
return {
|
217 |
+
"success": False,
|
218 |
+
"error": "JSON parsing error",
|
219 |
+
"stdout": result.stdout,
|
220 |
+
"stderr": result.stderr
|
221 |
+
}
|
222 |
+
|
223 |
+
except Exception as e:
|
224 |
+
return {
|
225 |
+
"success": False,
|
226 |
+
"error": f"Error executing profile scan: {str(e)}"
|
227 |
+
}
|
228 |
+
|
229 |
+
# Create Gradio interfaces
|
230 |
+
with gr.Blocks(title="Bandit Security Scanner MCP") as demo:
|
231 |
+
gr.Markdown("# π Bandit Security Scanner")
|
232 |
+
gr.Markdown("Python code security analyzer with MCP support")
|
233 |
+
|
234 |
+
with gr.Tab("Basic Scanning"):
|
235 |
+
with gr.Row():
|
236 |
+
with gr.Column():
|
237 |
+
scan_type = gr.Radio(
|
238 |
+
choices=["code", "path"],
|
239 |
+
value="code",
|
240 |
+
label="Scan Type"
|
241 |
+
)
|
242 |
+
code_input = gr.Textbox(
|
243 |
+
lines=10,
|
244 |
+
placeholder="Enter Python code or path to file/directory...",
|
245 |
+
label="Code or Path"
|
246 |
+
)
|
247 |
+
severity = gr.Dropdown(
|
248 |
+
choices=["low", "medium", "high"],
|
249 |
+
value="low",
|
250 |
+
label="Minimum Severity Level"
|
251 |
+
)
|
252 |
+
confidence = gr.Dropdown(
|
253 |
+
choices=["low", "medium", "high"],
|
254 |
+
value="low",
|
255 |
+
label="Minimum Confidence Level"
|
256 |
+
)
|
257 |
+
output_format = gr.Dropdown(
|
258 |
+
choices=["json", "txt"],
|
259 |
+
value="json",
|
260 |
+
label="Output Format"
|
261 |
+
)
|
262 |
+
scan_btn = gr.Button("π Scan", variant="primary")
|
263 |
+
|
264 |
+
with gr.Column():
|
265 |
+
scan_output = gr.JSON(label="Scan Results")
|
266 |
+
|
267 |
+
scan_btn.click(
|
268 |
+
fn=bandit_scan,
|
269 |
+
inputs=[code_input, scan_type, severity, confidence, output_format],
|
270 |
+
outputs=scan_output
|
271 |
+
)
|
272 |
+
|
273 |
+
with gr.Tab("Baseline Management"):
|
274 |
+
with gr.Row():
|
275 |
+
with gr.Column():
|
276 |
+
baseline_path = gr.Textbox(
|
277 |
+
label="Project Path",
|
278 |
+
placeholder="/path/to/your/project"
|
279 |
+
)
|
280 |
+
baseline_file = gr.Textbox(
|
281 |
+
label="Baseline File Path",
|
282 |
+
placeholder="/path/to/baseline.json"
|
283 |
+
)
|
284 |
+
baseline_btn = gr.Button("π Create/Compare Baseline", variant="secondary")
|
285 |
+
|
286 |
+
with gr.Column():
|
287 |
+
baseline_output = gr.JSON(label="Baseline Results")
|
288 |
+
|
289 |
+
baseline_btn.click(
|
290 |
+
fn=bandit_baseline,
|
291 |
+
inputs=[baseline_path, baseline_file],
|
292 |
+
outputs=baseline_output
|
293 |
+
)
|
294 |
+
|
295 |
+
with gr.Tab("Profile Scanning"):
|
296 |
+
with gr.Row():
|
297 |
+
with gr.Column():
|
298 |
+
profile_path = gr.Textbox(
|
299 |
+
label="Project Path",
|
300 |
+
placeholder="/path/to/your/project"
|
301 |
+
)
|
302 |
+
profile_name = gr.Dropdown(
|
303 |
+
choices=["ShellInjection", "SqlInjection", "Crypto", "Subprocess"],
|
304 |
+
value="ShellInjection",
|
305 |
+
label="Security Profile"
|
306 |
+
)
|
307 |
+
profile_btn = gr.Button("π― Scan with Profile", variant="secondary")
|
308 |
+
|
309 |
+
with gr.Column():
|
310 |
+
profile_output = gr.JSON(label="Profile Scan Results")
|
311 |
+
|
312 |
+
profile_btn.click(
|
313 |
+
fn=bandit_profile_scan,
|
314 |
+
inputs=[profile_path, profile_name],
|
315 |
+
outputs=profile_output
|
316 |
+
)
|
317 |
+
|
318 |
+
with gr.Tab("Examples"):
|
319 |
+
gr.Markdown("""
|
320 |
+
## π¨ Vulnerable code examples for testing:
|
321 |
+
|
322 |
+
### 1. Using eval()
|
323 |
+
```python
|
324 |
+
user_input = "print('hello')"
|
325 |
+
eval(user_input) # B307: Use of possibly insecure function
|
326 |
+
```
|
327 |
+
|
328 |
+
### 2. Hardcoded password
|
329 |
+
```python
|
330 |
+
password = "secret123" # B105: Possible hardcoded password
|
331 |
+
```
|
332 |
+
|
333 |
+
### 3. Insecure subprocess
|
334 |
+
```python
|
335 |
+
import subprocess
|
336 |
+
subprocess.call("ls -la", shell=True) # B602: subprocess call with shell=True
|
337 |
+
```
|
338 |
+
|
339 |
+
### 4. Using pickle
|
340 |
+
```python
|
341 |
+
import pickle
|
342 |
+
data = pickle.loads(user_data) # B301: Pickle usage
|
343 |
+
```
|
344 |
+
""")
|
345 |
+
|
346 |
+
if __name__ == "__main__":
|
347 |
+
demo.launch(mcp_server=True)
|
circle_test_mcp.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
MCP server for Circle Test - a tool for checking code against security policies
|
4 |
+
"""
|
5 |
+
|
6 |
+
import gradio as gr
|
7 |
+
import aiohttp
|
8 |
+
import asyncio
|
9 |
+
import ssl
|
10 |
+
import os
|
11 |
+
from typing import Dict, List
|
12 |
+
from dotenv import load_dotenv
|
13 |
+
|
14 |
+
# ΠΠ°Π³ΡΡΠΆΠ°Π΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ
|
15 |
+
load_dotenv()
|
16 |
+
|
17 |
+
# ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ URL ΠΈΠ· ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ
|
18 |
+
CIRCLE_API_URL = os.getenv('CIRCLE_API_URL', 'https://api.example.com/protect/check_violation')
|
19 |
+
|
20 |
+
async def check_violation(prompt: str, policies: Dict[str, str]) -> Dict:
|
21 |
+
"""
|
22 |
+
ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ ΠΊΠΎΠ΄ Π½Π° ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΠΈΠ΅ ΠΏΠΎΠ»ΠΈΡΠΈΠΊΠ°ΠΌ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΠΈ.
|
23 |
+
|
24 |
+
Args:
|
25 |
+
prompt (str): ΠΠΎΠ΄ Π΄Π»Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ
|
26 |
+
policies (Dict[str, str]): Π‘Π»ΠΎΠ²Π°ΡΡ ΠΏΠΎΠ»ΠΈΡΠΈΠΊ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΠΈ
|
27 |
+
|
28 |
+
Returns:
|
29 |
+
Dict: Π Π΅Π·ΡΠ»ΡΡΠ°ΡΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ
|
30 |
+
"""
|
31 |
+
try:
|
32 |
+
payload = {
|
33 |
+
"dialog": [
|
34 |
+
{
|
35 |
+
"role": "assistant",
|
36 |
+
"content": prompt
|
37 |
+
}
|
38 |
+
],
|
39 |
+
"policies": policies
|
40 |
+
}
|
41 |
+
|
42 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ SSL-ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ
|
43 |
+
ssl_context = ssl.create_default_context()
|
44 |
+
ssl_context.check_hostname = False
|
45 |
+
ssl_context.verify_mode = ssl.CERT_NONE
|
46 |
+
|
47 |
+
async with aiohttp.ClientSession() as session:
|
48 |
+
async with session.post(
|
49 |
+
CIRCLE_API_URL,
|
50 |
+
json=payload,
|
51 |
+
timeout=30,
|
52 |
+
ssl=ssl_context
|
53 |
+
) as response:
|
54 |
+
result = await response.json()
|
55 |
+
|
56 |
+
# ΠΡΠ΅ΠΎΠ±ΡΠ°Π·ΡΠ΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ Π² Π±ΠΎΠ»Π΅Π΅ ΡΠΈΡΠ°Π΅ΠΌΡΠΉ ΡΠΎΡΠΌΠ°Ρ
|
57 |
+
if 'policies' in result:
|
58 |
+
formatted_result = {}
|
59 |
+
for policy_num, value in result['policies'].items():
|
60 |
+
policy_text = policies.get(policy_num, "Unknown policy")
|
61 |
+
formatted_result[policy_num] = {
|
62 |
+
"policy": policy_text,
|
63 |
+
"violation": "yes" if value == 1 else "no"
|
64 |
+
}
|
65 |
+
return {
|
66 |
+
"success": True,
|
67 |
+
"results": formatted_result
|
68 |
+
}
|
69 |
+
return {
|
70 |
+
"success": False,
|
71 |
+
"error": "Invalid response format"
|
72 |
+
}
|
73 |
+
|
74 |
+
except Exception as e:
|
75 |
+
return {
|
76 |
+
"success": False,
|
77 |
+
"error": f"Error checking violations: {str(e)}"
|
78 |
+
}
|
79 |
+
|
80 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Gradio ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ
|
81 |
+
with gr.Blocks(title="Circle Test MCP") as demo:
|
82 |
+
gr.Markdown("# π Circle Test Scanner")
|
83 |
+
gr.Markdown("Security policy compliance checker with MCP support")
|
84 |
+
|
85 |
+
with gr.Tab("Policy Check"):
|
86 |
+
with gr.Row():
|
87 |
+
with gr.Column():
|
88 |
+
code_input = gr.Textbox(
|
89 |
+
lines=10,
|
90 |
+
placeholder="Enter code to check...",
|
91 |
+
label="Code"
|
92 |
+
)
|
93 |
+
check_btn = gr.Button("π Check Policies", variant="primary")
|
94 |
+
|
95 |
+
with gr.Column():
|
96 |
+
check_output = gr.JSON(label="Check Results")
|
97 |
+
|
98 |
+
check_btn.click(
|
99 |
+
fn=check_violation,
|
100 |
+
inputs=[
|
101 |
+
code_input,
|
102 |
+
gr.State({
|
103 |
+
"1": "Presence of SPDX-License-Identifier with an ID not in the approved list, or missing SPDX tag in top-level LICENSE file.",
|
104 |
+
"2": "Presence of plaintext credentials (passwords, tokens, keys) in configuration files (YAML, JSON, .env, etc.).",
|
105 |
+
"3": "Presence of TODO or FIXME tags in comments inside non-test production code files.",
|
106 |
+
"4": "Presence of any string literal starting with http:// not wrapped in a validated secure-client.",
|
107 |
+
"5": "Presence of logging statements that output sensitive data (user PII, private keys, passwords, tokens) without masking or hashing.",
|
108 |
+
"6": "Presence of calls to deprecated or outdated APIs (functions or methods marked as deprecated).",
|
109 |
+
"7": "Presence of subprocess or os.system calls where user input is concatenated directly without proper sanitization or escaping.",
|
110 |
+
"8": "Presence of file read/write operations using paths derived directly from user input without normalization or path-traversal checks.",
|
111 |
+
"9": "Presence of SQL queries built using string concatenation with user input instead of parameterized queries or ORM methods.",
|
112 |
+
"10": "Presence of string literals matching absolute filesystem paths (e.g., \"/home/...\" or \"C:\\\\...\") rather than relative paths or environment variables.",
|
113 |
+
"11": "Presence of hostnames or URLs containing \"prod\", \"production\", or \"release\" that reference production databases or services in non-test code.",
|
114 |
+
"12": "Presence of dependencies in lock files (Pipfile.lock or requirements.txt) without exact version pins (using version ranges like \">=\" or \"~=\" without a fixed version).",
|
115 |
+
"13": "Presence of hashlib.md5(...) or any MD5-based hashing, since MD5 is cryptographically broken (use SHA-256 or better).",
|
116 |
+
"14": "Presence of pdb.set_trace() or other pdb imports, as debug statements should not remain in production code.",
|
117 |
+
"15": "Presence of logging.debug($SENSITIVE) or similar logging calls that output sensitive information without redaction.",
|
118 |
+
"16": "Presence of re.compile($USER_INPUT) where $USER_INPUT is unsanitized, since this can lead to ReDoS attacks.",
|
119 |
+
"17": "Presence of xml.etree.ElementTree.parse($USER_INPUT) without secure parsing, leading to XXE vulnerabilities.",
|
120 |
+
"18": "Presence of zipfile.ZipFile($USER_INPUT) or similar extraction calls on untrusted zips, which can cause path traversal.",
|
121 |
+
"19": "Presence of tarfile.open($USER_INPUT) on untrusted tar files, leading to path traversal vulnerabilities.",
|
122 |
+
"20": "Presence of os.chmod($PATH, 0o777) or equivalent setting overly permissive permissions, which is insecure.",
|
123 |
+
"21": "Presence of os.environ[$KEY] = $VALUE modifying environment variables at runtime, which can introduce security risks."
|
124 |
+
})
|
125 |
+
],
|
126 |
+
outputs=check_output
|
127 |
+
)
|
128 |
+
|
129 |
+
with gr.Tab("Examples"):
|
130 |
+
gr.Markdown("""
|
131 |
+
## π¨ Examples of code to check:
|
132 |
+
|
133 |
+
### 1. Insecure File Operations
|
134 |
+
```python
|
135 |
+
def read_file(filename):
|
136 |
+
with open(filename, "r") as f:
|
137 |
+
return f.read()
|
138 |
+
```
|
139 |
+
|
140 |
+
### 2. Hardcoded Credentials
|
141 |
+
```python
|
142 |
+
DB_PASSWORD = "secret123"
|
143 |
+
API_KEY = "sk_live_51H1h2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8"
|
144 |
+
```
|
145 |
+
|
146 |
+
### 3. Insecure Subprocess
|
147 |
+
```python
|
148 |
+
import subprocess
|
149 |
+
subprocess.call(f"ls {user_input}", shell=True)
|
150 |
+
```
|
151 |
+
""")
|
152 |
+
|
153 |
+
if __name__ == "__main__":
|
154 |
+
demo.launch(mcp_server=True)
|
detect_secrets_mcp.py
ADDED
@@ -0,0 +1,479 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
MCP server for detect-secrets - a tool for detecting secrets in code
|
4 |
+
"""
|
5 |
+
|
6 |
+
import gradio as gr
|
7 |
+
import subprocess
|
8 |
+
import json
|
9 |
+
import os
|
10 |
+
import tempfile
|
11 |
+
from typing import Dict, List, Optional
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
def detect_secrets_scan(
|
15 |
+
code_input: str,
|
16 |
+
scan_type: str = "code",
|
17 |
+
base64_limit: float = 3.0,
|
18 |
+
hex_limit: float = 2.0,
|
19 |
+
exclude_lines: str = "",
|
20 |
+
exclude_files: str = "",
|
21 |
+
exclude_secrets: str = "",
|
22 |
+
word_list: str = "",
|
23 |
+
output_format: str = "json"
|
24 |
+
) -> Dict:
|
25 |
+
"""
|
26 |
+
Scans code for secrets using detect-secrets.
|
27 |
+
|
28 |
+
Args:
|
29 |
+
code_input (str): Code to scan or path to file/directory
|
30 |
+
scan_type (str): Scan type - 'code' for direct code or 'path' for file/directory
|
31 |
+
base64_limit (float): Entropy limit for base64 strings (0.0-8.0)
|
32 |
+
hex_limit (float): Entropy limit for hex strings (0.0-8.0)
|
33 |
+
exclude_lines (str): Regex pattern for lines to exclude
|
34 |
+
exclude_files (str): Regex pattern for files to exclude
|
35 |
+
exclude_secrets (str): Regex pattern for secrets to exclude
|
36 |
+
word_list (str): Path to word list file
|
37 |
+
output_format (str): Output format - 'json' or 'txt'
|
38 |
+
|
39 |
+
Returns:
|
40 |
+
Dict: Scan results
|
41 |
+
"""
|
42 |
+
try:
|
43 |
+
print(f"Debug: Input code length: {len(code_input)}")
|
44 |
+
print(f"Debug: First 100 chars: {code_input[:100]}")
|
45 |
+
|
46 |
+
# Build detect-secrets command
|
47 |
+
cmd = ["detect-secrets", "scan"]
|
48 |
+
|
49 |
+
# Add entropy limits
|
50 |
+
cmd.extend(["--base64-limit", str(base64_limit)])
|
51 |
+
cmd.extend(["--hex-limit", str(hex_limit)])
|
52 |
+
|
53 |
+
# Add exclude patterns
|
54 |
+
if exclude_lines:
|
55 |
+
cmd.extend(["--exclude-lines", exclude_lines])
|
56 |
+
if exclude_files:
|
57 |
+
cmd.extend(["--exclude-files", exclude_files])
|
58 |
+
if exclude_secrets:
|
59 |
+
cmd.extend(["--exclude-secrets", exclude_secrets])
|
60 |
+
if word_list:
|
61 |
+
cmd.extend(["--word-list", word_list])
|
62 |
+
|
63 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ Π΄Π»Ρ ΡΠ»ΡΡΡΠ΅Π½ΠΈΡ ΠΎΠ±Π½Π°ΡΡΠΆΠ΅Π½ΠΈΡ
|
64 |
+
cmd.extend(["--force-use-all-plugins"]) # ΠΡΠΈΠ½ΡΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Π²ΡΠ΅ ΠΏΠ»Π°Π³ΠΈΠ½Ρ
|
65 |
+
cmd.extend(["--no-verify"]) # ΠΡΠΊΠ»ΡΡΠ°Π΅ΠΌ Π²Π΅ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ
|
66 |
+
cmd.extend(["--disable-filter", "detect_secrets.filters.gibberish.should_exclude_secret"]) # ΠΡΠΊΠ»ΡΡΠ°Π΅ΠΌ ΡΠΈΠ»ΡΡΡ Π±Π΅ΡΡΠΌΡΡΠ»Π΅Π½Π½ΠΎΠ³ΠΎ ΡΠ΅ΠΊΡΡΠ°
|
67 |
+
cmd.extend(["--disable-filter", "detect_secrets.filters.heuristic.is_likely_id_string"]) # ΠΡΠΊΠ»ΡΡΠ°Π΅ΠΌ ΡΠΈΠ»ΡΡΡ ID ΡΡΡΠΎΠΊ
|
68 |
+
cmd.extend(["--disable-filter", "detect_secrets.filters.heuristic.is_sequential_string"]) # ΠΡΠΊΠ»ΡΡΠ°Π΅ΠΌ ΡΠΈΠ»ΡΡΡ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°ΡΠ΅Π»ΡΠ½ΡΡ
ΡΡΡΠΎΠΊ
|
69 |
+
|
70 |
+
# Execute command with pipe
|
71 |
+
if scan_type == "code":
|
72 |
+
# ΠΠΎΡΠΌΠ°Π»ΠΈΠ·ΡΠ΅ΠΌ ΡΡΡΠΎΠΊΠΈ
|
73 |
+
lines = code_input.replace('\r\n', '\n').replace('\r', '\n').split('\n')
|
74 |
+
print(f"Debug: Number of lines: {len(lines)}")
|
75 |
+
|
76 |
+
all_results = {}
|
77 |
+
all_plugins = set()
|
78 |
+
|
79 |
+
for idx, line in enumerate(lines):
|
80 |
+
if not line.strip():
|
81 |
+
continue # ΠΏΡΠΎΠΏΡΡΠΊΠ°Π΅ΠΌ ΠΏΡΡΡΡΠ΅ ΡΡΡΠΎΠΊΠΈ
|
82 |
+
|
83 |
+
print(f"Debug: Scanning line {idx + 1}: {line}")
|
84 |
+
|
85 |
+
cmd_line = cmd.copy() # ΠΊΠΎΠΏΠΈΡ Π±Π°Π·ΠΎΠ²ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ
|
86 |
+
cmd_line.append("--string")
|
87 |
+
cmd_line.append(line)
|
88 |
+
|
89 |
+
print(f"Debug: Command: {' '.join(cmd_line)}")
|
90 |
+
|
91 |
+
result = subprocess.run(cmd_line, capture_output=True, text=True)
|
92 |
+
stdout, stderr = result.stdout, result.stderr
|
93 |
+
|
94 |
+
print(f"Debug: Line {idx + 1} stdout: {stdout}")
|
95 |
+
|
96 |
+
# ΠΠ°ΡΡΠΈΠΌ ΡΠ΅ΠΊΡΡΠΎΠ²ΡΠΉ Π²ΡΠ²ΠΎΠ΄
|
97 |
+
for output_line in stdout.split('\n'):
|
98 |
+
if ':' in output_line:
|
99 |
+
plugin, result = output_line.split(':', 1)
|
100 |
+
plugin = plugin.strip()
|
101 |
+
result = result.strip()
|
102 |
+
|
103 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΏΠ»Π°Π³ΠΈΠ½ Π² ΡΠΏΠΈΡΠΎΠΊ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½Π½ΡΡ
|
104 |
+
all_plugins.add(plugin)
|
105 |
+
|
106 |
+
# ΠΡΠ»ΠΈ ΠΏΠ»Π°Π³ΠΈΠ½ Π½Π°ΡΠ΅Π» ΡΠ΅ΠΊΡΠ΅Ρ
|
107 |
+
if result.lower() == 'true':
|
108 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ
|
109 |
+
if plugin not in all_results:
|
110 |
+
all_results[plugin] = []
|
111 |
+
|
112 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π·Π°ΠΏΠΈΡΡ ΠΎ Π½Π°ΠΉΠ΄Π΅Π½Π½ΠΎΠΌ ΡΠ΅ΠΊΡΠ΅ΡΠ΅
|
113 |
+
secret_info = {
|
114 |
+
"type": plugin,
|
115 |
+
"line_number": idx + 1,
|
116 |
+
"line": line,
|
117 |
+
"hashed_secret": f"hash_{line}",
|
118 |
+
"is_secret": True,
|
119 |
+
"is_verified": False
|
120 |
+
}
|
121 |
+
|
122 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΡΠ½ΡΡΠΎΠΏΠΈΡ, Π΅ΡΠ»ΠΈ ΠΎΠ½Π° ΡΠΊΠ°Π·Π°Π½Π°
|
123 |
+
if '(' in result:
|
124 |
+
entropy = result.split('(')[1].split(')')[0]
|
125 |
+
try:
|
126 |
+
secret_info["entropy"] = float(entropy)
|
127 |
+
except ValueError:
|
128 |
+
pass
|
129 |
+
|
130 |
+
all_results[plugin].append(secret_info)
|
131 |
+
|
132 |
+
# Π‘ΠΎΠ±ΠΈΡΠ°Π΅ΠΌ ΡΠΈΠ½Π°Π»ΡΠ½ΡΠΉ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ
|
133 |
+
final_output = {
|
134 |
+
"version": "1.5.0",
|
135 |
+
"plugins_used": [{"name": plugin} for plugin in sorted(all_plugins)],
|
136 |
+
"filters_used": [],
|
137 |
+
"results": all_results,
|
138 |
+
"generated_at": ""
|
139 |
+
}
|
140 |
+
|
141 |
+
print(f"Debug: Final results: {json.dumps(final_output, indent=2)}")
|
142 |
+
|
143 |
+
return {
|
144 |
+
"success": True,
|
145 |
+
"results": final_output,
|
146 |
+
"stderr": "",
|
147 |
+
"return_code": 0
|
148 |
+
}
|
149 |
+
else:
|
150 |
+
# ΠΠ»Ρ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ°ΠΉΠ»Π°/Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΎΠ±ΡΡΠ½ΡΠΉ ΡΠΏΠΎΡΠΎΠ±
|
151 |
+
if not os.path.exists(code_input):
|
152 |
+
return {
|
153 |
+
"error": f"Path not found: {code_input}",
|
154 |
+
"success": False
|
155 |
+
}
|
156 |
+
cmd.append(code_input)
|
157 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
158 |
+
stdout, stderr = result.stdout, result.stderr
|
159 |
+
return_code = result.returncode
|
160 |
+
|
161 |
+
# Process result
|
162 |
+
if output_format == "json":
|
163 |
+
try:
|
164 |
+
output_data = json.loads(stdout) if stdout else {}
|
165 |
+
return {
|
166 |
+
"success": True,
|
167 |
+
"results": output_data,
|
168 |
+
"stderr": stderr,
|
169 |
+
"return_code": return_code
|
170 |
+
}
|
171 |
+
except json.JSONDecodeError as e:
|
172 |
+
print(f"Debug: JSON parse error: {e}")
|
173 |
+
print(f"Debug: Raw stdout: {stdout}")
|
174 |
+
return {
|
175 |
+
"success": False,
|
176 |
+
"error": "JSON parsing error",
|
177 |
+
"stdout": stdout,
|
178 |
+
"stderr": stderr,
|
179 |
+
"return_code": return_code
|
180 |
+
}
|
181 |
+
else:
|
182 |
+
return {
|
183 |
+
"success": True,
|
184 |
+
"output": stdout,
|
185 |
+
"stderr": stderr,
|
186 |
+
"return_code": return_code
|
187 |
+
}
|
188 |
+
|
189 |
+
except Exception as e:
|
190 |
+
print(f"Debug: Exception: {str(e)}")
|
191 |
+
return {
|
192 |
+
"success": False,
|
193 |
+
"error": f"Error executing detect-secrets: {str(e)}"
|
194 |
+
}
|
195 |
+
|
196 |
+
def detect_secrets_baseline(
|
197 |
+
target_path: str,
|
198 |
+
baseline_file: str,
|
199 |
+
base64_limit: float = 4.5,
|
200 |
+
hex_limit: float = 3.0
|
201 |
+
) -> Dict:
|
202 |
+
"""
|
203 |
+
Creates or updates a baseline file for detect-secrets.
|
204 |
+
|
205 |
+
Args:
|
206 |
+
target_path (str): Path to code for analysis
|
207 |
+
baseline_file (str): Path to baseline file
|
208 |
+
base64_limit (float): Entropy limit for base64 strings
|
209 |
+
hex_limit (float): Entropy limit for hex strings
|
210 |
+
|
211 |
+
Returns:
|
212 |
+
Dict: Result of baseline creation/update
|
213 |
+
"""
|
214 |
+
try:
|
215 |
+
if not os.path.exists(target_path):
|
216 |
+
return {
|
217 |
+
"error": f"Path not found: {target_path}",
|
218 |
+
"success": False
|
219 |
+
}
|
220 |
+
|
221 |
+
# Build command
|
222 |
+
cmd = ["detect-secrets", "scan"]
|
223 |
+
|
224 |
+
# Add entropy limits
|
225 |
+
cmd.extend(["--base64-limit", str(base64_limit)])
|
226 |
+
cmd.extend(["--hex-limit", str(hex_limit)])
|
227 |
+
|
228 |
+
# Add baseline file if exists
|
229 |
+
if os.path.exists(baseline_file):
|
230 |
+
cmd.extend(["--baseline", baseline_file])
|
231 |
+
|
232 |
+
# Add scan target
|
233 |
+
cmd.append(target_path)
|
234 |
+
|
235 |
+
# Execute command
|
236 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
237 |
+
|
238 |
+
# Save output to baseline file
|
239 |
+
with open(baseline_file, 'w') as f:
|
240 |
+
f.write(result.stdout)
|
241 |
+
|
242 |
+
return {
|
243 |
+
"success": True,
|
244 |
+
"action": "created" if not os.path.exists(baseline_file) else "updated",
|
245 |
+
"message": f"Baseline file {'created' if not os.path.exists(baseline_file) else 'updated'}: {baseline_file}",
|
246 |
+
"return_code": result.returncode,
|
247 |
+
"stderr": result.stderr
|
248 |
+
}
|
249 |
+
|
250 |
+
except Exception as e:
|
251 |
+
return {
|
252 |
+
"success": False,
|
253 |
+
"error": f"Error working with baseline: {str(e)}"
|
254 |
+
}
|
255 |
+
|
256 |
+
def detect_secrets_audit(
|
257 |
+
baseline_file: str,
|
258 |
+
show_stats: bool = False,
|
259 |
+
show_report: bool = False,
|
260 |
+
only_real: bool = False,
|
261 |
+
only_false: bool = False
|
262 |
+
) -> Dict:
|
263 |
+
"""
|
264 |
+
Audits a detect-secrets baseline file.
|
265 |
+
|
266 |
+
Args:
|
267 |
+
baseline_file (str): Path to baseline file
|
268 |
+
show_stats (bool): Show statistics
|
269 |
+
show_report (bool): Show report
|
270 |
+
only_real (bool): Only show real secrets
|
271 |
+
only_false (bool): Only show false positives
|
272 |
+
|
273 |
+
Returns:
|
274 |
+
Dict: Audit results
|
275 |
+
"""
|
276 |
+
try:
|
277 |
+
if not os.path.exists(baseline_file):
|
278 |
+
return {
|
279 |
+
"error": f"Baseline file not found: {baseline_file}",
|
280 |
+
"success": False
|
281 |
+
}
|
282 |
+
|
283 |
+
# Build command
|
284 |
+
cmd = ["detect-secrets", "audit"]
|
285 |
+
|
286 |
+
if show_stats:
|
287 |
+
cmd.append("--stats")
|
288 |
+
if show_report:
|
289 |
+
cmd.append("--report")
|
290 |
+
if only_real:
|
291 |
+
cmd.append("--only-real")
|
292 |
+
if only_false:
|
293 |
+
cmd.append("--only-false")
|
294 |
+
|
295 |
+
cmd.append(baseline_file)
|
296 |
+
|
297 |
+
# Execute command
|
298 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
299 |
+
|
300 |
+
return {
|
301 |
+
"success": True,
|
302 |
+
"output": result.stdout,
|
303 |
+
"stderr": result.stderr,
|
304 |
+
"return_code": result.returncode
|
305 |
+
}
|
306 |
+
|
307 |
+
except Exception as e:
|
308 |
+
return {
|
309 |
+
"success": False,
|
310 |
+
"error": f"Error auditing baseline: {str(e)}"
|
311 |
+
}
|
312 |
+
|
313 |
+
# Create Gradio interface
|
314 |
+
with gr.Blocks(title="Detect Secrets MCP") as demo:
|
315 |
+
gr.Markdown("# π Detect Secrets Scanner")
|
316 |
+
gr.Markdown("Secret detection tool with MCP support")
|
317 |
+
|
318 |
+
with gr.Tab("Basic Scanning"):
|
319 |
+
with gr.Row():
|
320 |
+
with gr.Column():
|
321 |
+
scan_type = gr.Radio(
|
322 |
+
choices=["code", "path"],
|
323 |
+
value="code",
|
324 |
+
label="Scan Type"
|
325 |
+
)
|
326 |
+
code_input = gr.Textbox(
|
327 |
+
lines=10,
|
328 |
+
placeholder="Enter code or path to scan...",
|
329 |
+
label="Code or Path"
|
330 |
+
)
|
331 |
+
base64_limit = gr.Slider(
|
332 |
+
minimum=0.0,
|
333 |
+
maximum=8.0,
|
334 |
+
value=4.5,
|
335 |
+
step=0.1,
|
336 |
+
label="Base64 Entropy Limit"
|
337 |
+
)
|
338 |
+
hex_limit = gr.Slider(
|
339 |
+
minimum=0.0,
|
340 |
+
maximum=8.0,
|
341 |
+
value=3.0,
|
342 |
+
step=0.1,
|
343 |
+
label="Hex Entropy Limit"
|
344 |
+
)
|
345 |
+
exclude_lines = gr.Textbox(
|
346 |
+
label="Exclude Lines Pattern (regex)"
|
347 |
+
)
|
348 |
+
exclude_files = gr.Textbox(
|
349 |
+
label="Exclude Files Pattern (regex)"
|
350 |
+
)
|
351 |
+
exclude_secrets = gr.Textbox(
|
352 |
+
label="Exclude Secrets Pattern (regex)"
|
353 |
+
)
|
354 |
+
word_list = gr.Textbox(
|
355 |
+
label="Word List File Path"
|
356 |
+
)
|
357 |
+
output_format = gr.Dropdown(
|
358 |
+
choices=["json", "txt"],
|
359 |
+
value="json",
|
360 |
+
label="Output Format"
|
361 |
+
)
|
362 |
+
scan_btn = gr.Button("π Scan", variant="primary")
|
363 |
+
|
364 |
+
with gr.Column():
|
365 |
+
scan_output = gr.JSON(label="Scan Results")
|
366 |
+
|
367 |
+
scan_btn.click(
|
368 |
+
fn=detect_secrets_scan,
|
369 |
+
inputs=[
|
370 |
+
code_input, scan_type, base64_limit, hex_limit,
|
371 |
+
exclude_lines, exclude_files, exclude_secrets,
|
372 |
+
word_list, output_format
|
373 |
+
],
|
374 |
+
outputs=scan_output
|
375 |
+
)
|
376 |
+
|
377 |
+
with gr.Tab("Baseline Management"):
|
378 |
+
with gr.Row():
|
379 |
+
with gr.Column():
|
380 |
+
baseline_path = gr.Textbox(
|
381 |
+
label="Project Path",
|
382 |
+
placeholder="/path/to/your/project"
|
383 |
+
)
|
384 |
+
baseline_file = gr.Textbox(
|
385 |
+
label="Baseline File Path",
|
386 |
+
placeholder="/path/to/.secrets.baseline"
|
387 |
+
)
|
388 |
+
baseline_base64_limit = gr.Slider(
|
389 |
+
minimum=0.0,
|
390 |
+
maximum=8.0,
|
391 |
+
value=4.5,
|
392 |
+
step=0.1,
|
393 |
+
label="Base64 Entropy Limit"
|
394 |
+
)
|
395 |
+
baseline_hex_limit = gr.Slider(
|
396 |
+
minimum=0.0,
|
397 |
+
maximum=8.0,
|
398 |
+
value=3.0,
|
399 |
+
step=0.1,
|
400 |
+
label="Hex Entropy Limit"
|
401 |
+
)
|
402 |
+
baseline_btn = gr.Button("π Create/Update Baseline", variant="secondary")
|
403 |
+
|
404 |
+
with gr.Column():
|
405 |
+
baseline_output = gr.JSON(label="Baseline Results")
|
406 |
+
|
407 |
+
baseline_btn.click(
|
408 |
+
fn=detect_secrets_baseline,
|
409 |
+
inputs=[
|
410 |
+
baseline_path, baseline_file,
|
411 |
+
baseline_base64_limit, baseline_hex_limit
|
412 |
+
],
|
413 |
+
outputs=baseline_output
|
414 |
+
)
|
415 |
+
|
416 |
+
with gr.Tab("Baseline Audit"):
|
417 |
+
with gr.Row():
|
418 |
+
with gr.Column():
|
419 |
+
audit_baseline = gr.Textbox(
|
420 |
+
label="Baseline File Path",
|
421 |
+
placeholder="/path/to/.secrets.baseline"
|
422 |
+
)
|
423 |
+
show_stats = gr.Checkbox(
|
424 |
+
label="Show Statistics",
|
425 |
+
value=False
|
426 |
+
)
|
427 |
+
show_report = gr.Checkbox(
|
428 |
+
label="Show Report",
|
429 |
+
value=False
|
430 |
+
)
|
431 |
+
only_real = gr.Checkbox(
|
432 |
+
label="Only Real Secrets",
|
433 |
+
value=False
|
434 |
+
)
|
435 |
+
only_false = gr.Checkbox(
|
436 |
+
label="Only False Positives",
|
437 |
+
value=False
|
438 |
+
)
|
439 |
+
audit_btn = gr.Button("π Audit Baseline", variant="secondary")
|
440 |
+
|
441 |
+
with gr.Column():
|
442 |
+
audit_output = gr.JSON(label="Audit Results")
|
443 |
+
|
444 |
+
audit_btn.click(
|
445 |
+
fn=detect_secrets_audit,
|
446 |
+
inputs=[
|
447 |
+
audit_baseline, show_stats,
|
448 |
+
show_report, only_real, only_false
|
449 |
+
],
|
450 |
+
outputs=audit_output
|
451 |
+
)
|
452 |
+
|
453 |
+
with gr.Tab("Examples"):
|
454 |
+
gr.Markdown("""
|
455 |
+
## π¨ Examples of secrets that can be detected:
|
456 |
+
|
457 |
+
### 1. API Keys
|
458 |
+
```python
|
459 |
+
API_KEY = "sk_live_51H1h2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8"
|
460 |
+
```
|
461 |
+
|
462 |
+
### 2. Passwords
|
463 |
+
```python
|
464 |
+
password = "SuperSecret123!" # High entropy string
|
465 |
+
```
|
466 |
+
|
467 |
+
### 3. Private Keys
|
468 |
+
```python
|
469 |
+
private_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA..."
|
470 |
+
```
|
471 |
+
|
472 |
+
### 4. OAuth Tokens
|
473 |
+
```python
|
474 |
+
oauth_token = "ya29.a0AfB_byC..."
|
475 |
+
```
|
476 |
+
""")
|
477 |
+
|
478 |
+
if __name__ == "__main__":
|
479 |
+
demo.launch(mcp_server=True)
|
docker-compose.yml
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: '3.8'
|
2 |
+
|
3 |
+
services:
|
4 |
+
|
5 |
+
# Bandit Security Scanner
|
6 |
+
bandit-security-scanner:
|
7 |
+
build:
|
8 |
+
context: .
|
9 |
+
dockerfile: docker/bandit.Dockerfile
|
10 |
+
container_name: bandit-mcp-server
|
11 |
+
ports:
|
12 |
+
- "${BANDIT_EXTERNAL_PORT:-7861}:${BANDIT_INTERNAL_PORT:-7861}"
|
13 |
+
environment:
|
14 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
15 |
+
- GRADIO_SERVER_PORT=${BANDIT_INTERNAL_PORT:-7861}
|
16 |
+
- APP_NAME=Bandit Security Scanner MCP
|
17 |
+
volumes:
|
18 |
+
- ./scan_data:/app/scan_data
|
19 |
+
- ./reports:/app/reports
|
20 |
+
- ./projects:/app/projects
|
21 |
+
restart: unless-stopped
|
22 |
+
networks:
|
23 |
+
- mcp-network
|
24 |
+
labels:
|
25 |
+
- "application=bandit-security-scanner"
|
26 |
+
- "service=mcp-server"
|
27 |
+
|
28 |
+
# Detect Secrets Scanner
|
29 |
+
detect-secrets-scanner:
|
30 |
+
build:
|
31 |
+
context: .
|
32 |
+
dockerfile: docker/detect_secrets.Dockerfile
|
33 |
+
container_name: detect-secrets-mcp-server
|
34 |
+
ports:
|
35 |
+
- "${DETECT_SECRETS_EXTERNAL_PORT:-7862}:${DETECT_SECRETS_INTERNAL_PORT:-7862}"
|
36 |
+
environment:
|
37 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
38 |
+
- GRADIO_SERVER_PORT=${DETECT_SECRETS_INTERNAL_PORT:-7862}
|
39 |
+
- APP_NAME=Detect Secrets MCP
|
40 |
+
volumes:
|
41 |
+
- ./scan_data:/app/scan_data
|
42 |
+
- ./reports:/app/reports
|
43 |
+
- ./projects:/app/projects
|
44 |
+
restart: unless-stopped
|
45 |
+
networks:
|
46 |
+
- mcp-network
|
47 |
+
labels:
|
48 |
+
- "application=detect-secrets-scanner"
|
49 |
+
- "service=mcp-server"
|
50 |
+
|
51 |
+
# Pip Audit Scanner
|
52 |
+
pip-audit-scanner:
|
53 |
+
build:
|
54 |
+
context: .
|
55 |
+
dockerfile: docker/pip_audit.Dockerfile
|
56 |
+
container_name: pip-audit-mcp-server
|
57 |
+
ports:
|
58 |
+
- "${PIP_AUDIT_EXTERNAL_PORT:-7863}:${PIP_AUDIT_INTERNAL_PORT:-7863}"
|
59 |
+
environment:
|
60 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
61 |
+
- GRADIO_SERVER_PORT=${PIP_AUDIT_INTERNAL_PORT:-7863}
|
62 |
+
- APP_NAME=Pip Audit MCP
|
63 |
+
volumes:
|
64 |
+
- ./scan_data:/app/scan_data
|
65 |
+
- ./reports:/app/reports
|
66 |
+
- ./projects:/app/projects
|
67 |
+
restart: unless-stopped
|
68 |
+
networks:
|
69 |
+
- mcp-network
|
70 |
+
labels:
|
71 |
+
- "application=pip-audit-scanner"
|
72 |
+
- "service=mcp-server"
|
73 |
+
|
74 |
+
# Circle Test Scanner
|
75 |
+
circle-test-scanner:
|
76 |
+
build:
|
77 |
+
context: .
|
78 |
+
dockerfile: docker/circle_test.Dockerfile
|
79 |
+
container_name: circle-test-mcp-server
|
80 |
+
ports:
|
81 |
+
- "${CIRCLE_TEST_EXTERNAL_PORT:-7864}:${CIRCLE_TEST_INTERNAL_PORT:-7864}"
|
82 |
+
environment:
|
83 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
84 |
+
- GRADIO_SERVER_PORT=${CIRCLE_TEST_INTERNAL_PORT:-7864}
|
85 |
+
- APP_NAME=Circle Test MCP
|
86 |
+
volumes:
|
87 |
+
- ./scan_data:/app/scan_data
|
88 |
+
- ./reports:/app/reports
|
89 |
+
- ./projects:/app/projects
|
90 |
+
restart: unless-stopped
|
91 |
+
networks:
|
92 |
+
- mcp-network
|
93 |
+
labels:
|
94 |
+
- "application=circle-test-scanner"
|
95 |
+
- "service=mcp-server"
|
96 |
+
|
97 |
+
# Semgrep Scanner
|
98 |
+
semgrep-scanner:
|
99 |
+
build:
|
100 |
+
context: .
|
101 |
+
dockerfile: docker/semgrep.Dockerfile
|
102 |
+
container_name: semgrep-mcp-server
|
103 |
+
ports:
|
104 |
+
- "${SEMGREP_EXTERNAL_PORT:-7865}:${SEMGREP_INTERNAL_PORT:-7865}"
|
105 |
+
environment:
|
106 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
107 |
+
- GRADIO_SERVER_PORT=${SEMGREP_INTERNAL_PORT:-7865}
|
108 |
+
- APP_NAME=Semgrep MCP
|
109 |
+
volumes:
|
110 |
+
- ./scan_data:/app/scan_data
|
111 |
+
- ./reports:/app/reports
|
112 |
+
- ./projects:/app/projects
|
113 |
+
restart: unless-stopped
|
114 |
+
networks:
|
115 |
+
- mcp-network
|
116 |
+
labels:
|
117 |
+
- "application=semgrep-scanner"
|
118 |
+
- "service=mcp-server"
|
119 |
+
|
120 |
+
# Main Security Tools Agent
|
121 |
+
security-tools-agent:
|
122 |
+
build:
|
123 |
+
context: .
|
124 |
+
dockerfile: docker/agent.Dockerfile
|
125 |
+
container_name: security-tools-mcp-agent
|
126 |
+
ports:
|
127 |
+
- "${AGENT_EXTERNAL_PORT:-7860}:${AGENT_INTERNAL_PORT:-7860}"
|
128 |
+
environment:
|
129 |
+
- GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME:-0.0.0.0}
|
130 |
+
- GRADIO_SERVER_PORT=${AGENT_INTERNAL_PORT:-7860}
|
131 |
+
- APP_NAME=Security Tools MCP Agent
|
132 |
+
- NEBIUS_API_KEY=${NEBIUS_API_KEY}
|
133 |
+
volumes:
|
134 |
+
- ./scan_data:/app/scan_data
|
135 |
+
- ./reports:/app/reports
|
136 |
+
- ./projects:/app/projects
|
137 |
+
depends_on:
|
138 |
+
- bandit-security-scanner
|
139 |
+
- detect-secrets-scanner
|
140 |
+
- pip-audit-scanner
|
141 |
+
- circle-test-scanner
|
142 |
+
- semgrep-scanner
|
143 |
+
restart: unless-stopped
|
144 |
+
networks:
|
145 |
+
- mcp-network
|
146 |
+
labels:
|
147 |
+
- "application=security-tools-agent"
|
148 |
+
- "service=main-agent"
|
149 |
+
|
150 |
+
networks:
|
151 |
+
mcp-network:
|
152 |
+
driver: bridge
|
153 |
+
|
154 |
+
volumes:
|
155 |
+
scan_data:
|
156 |
+
reports:
|
157 |
+
projects:
|
docker/agent.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Security Tools MCP Agent - Main Application"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="security-tools-mcp-agent"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements Π΄Π»Ρ Π°Π³Π΅Π½ΡΠ°
|
20 |
+
COPY agent_requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY main.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Agent
|
30 |
+
ENV GRADIO_SERVER_PORT=7860
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Security Tools MCP Agent"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "main.py"]
|
docker/bandit.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Bandit Security Scanner MCP Server with Gradio Web Interface"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="bandit-mcp"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements
|
20 |
+
COPY requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY bandit_mcp.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Bandit MCP
|
30 |
+
ENV GRADIO_SERVER_PORT=7861
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Bandit Security Scanner MCP"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "bandit_mcp.py"]
|
docker/circle_test.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Circle Test MCP Server with Gradio Web Interface"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="circle-test-mcp"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements
|
20 |
+
COPY requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY circle_test_mcp.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Circle Test MCP
|
30 |
+
ENV GRADIO_SERVER_PORT=7864
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Circle Test MCP"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "circle_test_mcp.py"]
|
docker/detect_secrets.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Detect Secrets MCP Server with Gradio Web Interface"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="detect-secrets-mcp"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements
|
20 |
+
COPY requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY detect_secrets_mcp.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Detect Secrets MCP
|
30 |
+
ENV GRADIO_SERVER_PORT=7862
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Detect Secrets MCP"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "detect_secrets_mcp.py"]
|
docker/pip_audit.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Pip Audit MCP Server with Gradio Web Interface"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="pip-audit-mcp"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements
|
20 |
+
COPY requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY pip_audit_mcp.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Pip Audit MCP
|
30 |
+
ENV GRADIO_SERVER_PORT=7863
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Pip Audit MCP"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "pip_audit_mcp.py"]
|
docker/semgrep.Dockerfile
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# ΠΠ΅ΡΠ°Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π·Π°
|
4 |
+
LABEL maintainer="VulnBuster"
|
5 |
+
LABEL description="Semgrep MCP Server with Gradio Web Interface"
|
6 |
+
LABEL version="1.0"
|
7 |
+
LABEL application="semgrep-mcp"
|
8 |
+
|
9 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΠ½ΡΡ
Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
10 |
+
RUN apt-get update && apt-get install -y \
|
11 |
+
git \
|
12 |
+
curl \
|
13 |
+
build-essential \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΠΎΡΠ΅ΠΉ Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
17 |
+
WORKDIR /app
|
18 |
+
|
19 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ requirements
|
20 |
+
COPY requirements.txt ./requirements.txt
|
21 |
+
|
22 |
+
# Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Python Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
|
23 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
24 |
+
pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°
|
27 |
+
COPY semgrep_mcp.py .
|
28 |
+
|
29 |
+
# ΠΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ Semgrep MCP
|
30 |
+
ENV GRADIO_SERVER_PORT=7865
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
ENV APP_NAME="Semgrep MCP"
|
33 |
+
|
34 |
+
# ΠΡΠΊΡΡΡΠΈΠ΅ ΠΏΠΎΡΡΠ°
|
35 |
+
EXPOSE $GRADIO_SERVER_PORT
|
36 |
+
|
37 |
+
# Healthcheck
|
38 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
39 |
+
CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/ || exit 1
|
40 |
+
|
41 |
+
# ΠΠΎΠΌΠ°Π½Π΄Π° Π·Π°ΠΏΡΡΠΊΠ°
|
42 |
+
CMD ["python", "semgrep_mcp.py"]
|
main.py
ADDED
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import os
|
3 |
+
import tempfile
|
4 |
+
import gradio as gr
|
5 |
+
from textwrap import dedent
|
6 |
+
from agno.agent import Agent
|
7 |
+
from agno.tools.mcp import MCPTools
|
8 |
+
from agno.models.nebius import Nebius
|
9 |
+
from mcp import ClientSession
|
10 |
+
from mcp.client.sse import sse_client
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
import base64
|
13 |
+
import difflib
|
14 |
+
import re
|
15 |
+
import subprocess
|
16 |
+
import sys
|
17 |
+
import shutil
|
18 |
+
import time
|
19 |
+
import aiohttp
|
20 |
+
import logging
|
21 |
+
import signal
|
22 |
+
import socket
|
23 |
+
import json
|
24 |
+
|
25 |
+
|
26 |
+
# ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
|
27 |
+
logging.basicConfig(
|
28 |
+
level=logging.DEBUG,
|
29 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
30 |
+
)
|
31 |
+
logger = logging.getLogger(__name__)
|
32 |
+
|
33 |
+
def clean_and_validate_json(raw_text: str) -> str:
|
34 |
+
"""
|
35 |
+
ΠΠ³ΡΠ΅ΡΡΠΈΠ²Π½ΠΎ ΠΎΡΠΈΡΠ°Π΅Ρ ΠΈ Π²Π°Π»ΠΈΠ΄ΠΈΡΡΠ΅Ρ JSON ΠΈΠ· ΡΠ΅ΠΊΡΡΠ°
|
36 |
+
"""
|
37 |
+
logger.debug(f"ΠΠΎΠΏΡΡΠΊΠ° ΠΎΡΠΈΡΡΠΊΠΈ JSON ΠΈΠ· ΡΠ΅ΠΊΡΡΠ° Π΄Π»ΠΈΠ½ΠΎΠΉ {len(raw_text)}")
|
38 |
+
|
39 |
+
# Π£Π±ΠΈΡΠ°Π΅ΠΌ Π²ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΠ΅ Π±Π»ΠΎΠΊΠΈ Π΄ΡΠΌΠ°Π½ΠΈΡ ΠΈ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅Π²
|
40 |
+
cleaned = re.sub(r"<think>.*?</think>", "", raw_text, flags=re.DOTALL)
|
41 |
+
cleaned = re.sub(r"```json\s*", "", cleaned)
|
42 |
+
cleaned = re.sub(r"```\s*", "", cleaned)
|
43 |
+
cleaned = cleaned.strip()
|
44 |
+
|
45 |
+
# ΠΡΠ΅ΠΌ JSON ΠΎΠ±ΡΠ΅ΠΊΡ ΠΎΡ ΠΏΠ΅ΡΠ²ΠΎΠΉ { Π΄ΠΎ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠ΅ΠΉ }
|
46 |
+
start_idx = cleaned.find("{")
|
47 |
+
if start_idx == -1:
|
48 |
+
return raw_text
|
49 |
+
|
50 |
+
# ΠΠ°ΠΉΠ΄Π΅ΠΌ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΡΡ Π·Π°ΠΊΡΡΠ²Π°ΡΡΡΡ ΡΠΊΠΎΠ±ΠΊΡ
|
51 |
+
bracket_count = 0
|
52 |
+
end_idx = -1
|
53 |
+
|
54 |
+
for i in range(start_idx, len(cleaned)):
|
55 |
+
if cleaned[i] == '{':
|
56 |
+
bracket_count += 1
|
57 |
+
elif cleaned[i] == '}':
|
58 |
+
bracket_count -= 1
|
59 |
+
if bracket_count == 0:
|
60 |
+
end_idx = i
|
61 |
+
break
|
62 |
+
|
63 |
+
if end_idx == -1:
|
64 |
+
return raw_text
|
65 |
+
|
66 |
+
# ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅ΠΌ JSON ΡΠ°ΡΡΡ
|
67 |
+
json_part = cleaned[start_idx:end_idx + 1]
|
68 |
+
|
69 |
+
try:
|
70 |
+
# ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΡΠ²Π»ΡΠ΅ΡΡΡ Π»ΠΈ ΡΡΠΎ Π²Π°Π»ΠΈΠ΄Π½ΡΠΌ JSON
|
71 |
+
json.loads(json_part)
|
72 |
+
logger.debug("JSON ΡΡΠΏΠ΅ΡΠ½ΠΎ ΠΎΡΠΈΡΠ΅Π½ ΠΈ Π²Π°Π»ΠΈΠ΄ΠΈΡΠΎΠ²Π°Π½")
|
73 |
+
return json_part
|
74 |
+
except json.JSONDecodeError as e:
|
75 |
+
logger.debug(f"ΠΡΠΈΠ±ΠΊΠ° ΠΏΡΠΈ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΠΈ ΠΎΡΠΈΡΠ΅Π½Π½ΠΎΠ³ΠΎ JSON: {e}")
|
76 |
+
return raw_text
|
77 |
+
|
78 |
+
def standardize_mcp_response(response_text: str, server_name: str) -> str:
|
79 |
+
"""
|
80 |
+
Π‘ΡΠ°Π½Π΄Π°ΡΡΠΈΠ·ΠΈΡΡΠ΅Ρ ΠΎΡΠ²Π΅ΡΡ ΠΎΡ ΡΠ°Π·Π½ΡΡ
MCP ΡΠ΅ΡΠ²Π΅ΡΠΎΠ² Π² Π΅Π΄ΠΈΠ½ΡΠΉ ΡΠΎΡΠΌΠ°Ρ
|
81 |
+
"""
|
82 |
+
try:
|
83 |
+
# Π‘Π½Π°ΡΠ°Π»Π° ΠΏΡΡΠ°Π΅ΠΌΡΡ ΠΏΠ°ΡΡΠΈΡΡ ΠΊΠ°ΠΊ JSON
|
84 |
+
parsed = json.loads(response_text)
|
85 |
+
|
86 |
+
# ΠΠ»Ρ Circle Test - ΠΎΡΠ²Π΅ΡΡ ΠΌΠΎΠ³ΡΡ ΠΏΡΠΈΡ
ΠΎΠ΄ΠΈΡΡ Π² ΡΠΎΡΠΌΠ°ΡΠ΅ {"results": [...]}
|
87 |
+
if server_name == "circle_test":
|
88 |
+
if isinstance(parsed, dict) and "results" in parsed:
|
89 |
+
return json.dumps(parsed, ensure_ascii=False)
|
90 |
+
elif isinstance(parsed, list):
|
91 |
+
# ΠΡΠ»ΠΈ ΠΏΡΠΈΡΠ΅Π» ΡΠΏΠΈΡΠΎΠΊ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² Π±Π΅Π· ΠΎΠ±Π΅ΡΡΠΊΠΈ
|
92 |
+
standardized = {"results": parsed}
|
93 |
+
return json.dumps(standardized, ensure_ascii=False)
|
94 |
+
|
95 |
+
# ΠΠ»Ρ Bandit - ΠΎΡΠ²Π΅ΡΡ ΠΌΠΎΠ³ΡΡ ΠΏΡΠΈΡ
ΠΎΠ΄ΠΈΡΡ Π² ΡΠΎΡΠΌΠ°ΡΠ΅ {"results": [...], "metrics": {...}}
|
96 |
+
elif server_name == "bandit":
|
97 |
+
if isinstance(parsed, dict):
|
98 |
+
return json.dumps(parsed, ensure_ascii=False)
|
99 |
+
elif isinstance(parsed, list):
|
100 |
+
# ΠΡΠ»ΠΈ ΠΏΡΠΈΡΠ΅Π» ΡΠΏΠΈΡΠΎΠΊ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² Π±Π΅Π· ΠΎΠ±Π΅ΡΡΠΊΠΈ
|
101 |
+
standardized = {"results": parsed}
|
102 |
+
return json.dumps(standardized, ensure_ascii=False)
|
103 |
+
|
104 |
+
# ΠΠ»Ρ Π΄ΡΡΠ³ΠΈΡ
ΡΠ΅ΡΠ²Π΅ΡΠΎΠ² - Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡΡ, Π΅ΡΠ»ΠΈ JSON Π²Π°Π»ΠΈΠ΄Π½ΡΠΉ
|
105 |
+
return json.dumps(parsed, ensure_ascii=False)
|
106 |
+
|
107 |
+
except json.JSONDecodeError:
|
108 |
+
# ΠΡΠ»ΠΈ Π½Π΅ JSON, Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ΠΎΡΠΈΠ³ΠΈΠ½Π°Π»ΡΠ½ΡΡ ΡΡΡΠΎΠΊΡ
|
109 |
+
logger.debug(f"ΠΡΠ²Π΅Ρ ΠΎΡ {server_name} Π½Π΅ ΡΠ²Π»ΡΠ΅ΡΡΡ Π²Π°Π»ΠΈΠ΄Π½ΡΠΌ JSON")
|
110 |
+
return response_text
|
111 |
+
|
112 |
+
def extract_json_payload(raw: str) -> str:
|
113 |
+
"""
|
114 |
+
ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ ΠΏΠ΅ΡΠ²ΡΠΉ JSON ΠΎΠ±ΡΠ΅ΠΊΡ {...} ΠΈΠ· ΡΡΡΠΎΠΊΠΈ, ΡΠ΄Π°Π»ΡΡ Markdown-ΡΠ°Π·ΠΌΠ΅ΡΠΊΡ ΠΈ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΡΠ΅ΠΊΡΡ.
|
115 |
+
"""
|
116 |
+
logger.debug(f"ΠΡΡ
ΠΎΠ΄Π½Π°Ρ ΡΡΡΠΎΠΊΠ° Π΄Π»Ρ ΠΈΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΡ JSON (Π΄Π»ΠΈΠ½Π°: {len(raw)}): {raw[:500]}...")
|
117 |
+
|
118 |
+
# Π£Π±ΠΈΡΠ°Π΅ΠΌ Π±Π»ΠΎΠΊΠΈ <think>
|
119 |
+
raw = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL).strip()
|
120 |
+
|
121 |
+
# Π£Π±ΠΈΡΠ°Π΅ΠΌ markdown Π±Π»ΠΎΠΊΠΈ
|
122 |
+
raw = re.sub(r"```json\s*", "", raw)
|
123 |
+
raw = re.sub(r"```", "", raw)
|
124 |
+
|
125 |
+
# Π£Π±ΠΈΡΠ°Π΅ΠΌ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΠ΅ Π»ΠΈΡΠ½ΠΈΠ΅ ΡΠΈΠΌΠ²ΠΎΠ»Ρ Π² Π½Π°ΡΠ°Π»Π΅ ΠΈ ΠΊΠΎΠ½ΡΠ΅
|
126 |
+
raw = raw.strip()
|
127 |
+
|
128 |
+
logger.debug(f"ΠΠΎΡΠ»Π΅ ΠΎΡΠΈΡΡΠΊΠΈ markdown (Π΄Π»ΠΈΠ½Π°: {len(raw)}): {raw[:500]}...")
|
129 |
+
|
130 |
+
# ΠΡΠ΅ΠΌ ΠΏΠ΅ΡΠ²ΡΠΉ '{' ΠΈ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΡΡ Π΅ΠΌΡ Π·Π°ΠΊΡΡΠ²Π°ΡΡΡΡ '}'
|
131 |
+
start = raw.find("{")
|
132 |
+
if start == -1:
|
133 |
+
logger.warning("ΠΠ΅ Π½Π°ΠΉΠ΄Π΅Π½ ΠΎΡΠΊΡΡΠ²Π°ΡΡΠΈΠΉ ΡΠΈΠΌΠ²ΠΎΠ» '{'")
|
134 |
+
return raw
|
135 |
+
|
136 |
+
# ΠΡΠ΅ΠΌ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΡΡ Π·Π°ΠΊΡΡΠ²Π°ΡΡΡΡ ΡΠΊΠΎΠ±ΠΊΡ
|
137 |
+
bracket_count = 0
|
138 |
+
end = -1
|
139 |
+
for i in range(start, len(raw)):
|
140 |
+
if raw[i] == '{':
|
141 |
+
bracket_count += 1
|
142 |
+
elif raw[i] == '}':
|
143 |
+
bracket_count -= 1
|
144 |
+
if bracket_count == 0:
|
145 |
+
end = i
|
146 |
+
break
|
147 |
+
|
148 |
+
if end == -1:
|
149 |
+
logger.warning("ΠΠ΅ Π½Π°ΠΉΠ΄Π΅Π½ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠΉ Π·Π°ΠΊΡΡΠ²Π°ΡΡΠΈΠΉ ΡΠΈΠΌΠ²ΠΎΠ» '}'")
|
150 |
+
return raw
|
151 |
+
|
152 |
+
json_candidate = raw[start:end + 1]
|
153 |
+
logger.debug(f"ΠΠ·Π²Π»Π΅ΡΠ΅Π½Π½ΡΠΉ JSON ΠΊΠ°Π½Π΄ΠΈΠ΄Π°Ρ (Π΄Π»ΠΈΠ½Π°: {len(json_candidate)}): {json_candidate[:500]}...")
|
154 |
+
|
155 |
+
# ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ Π²Π°Π»ΠΈΠ΄Π½ΠΎΡΡΡ JSON ΠΏΠ΅ΡΠ΅Π΄ Π²ΠΎΠ·Π²ΡΠ°ΡΠΎΠΌ
|
156 |
+
try:
|
157 |
+
parsed = json.loads(json_candidate)
|
158 |
+
logger.debug("JSON ΡΡΠΏΠ΅ΡΠ½ΠΎ ΡΠ°ΡΠΏΠ°ΡΡΠ΅Π½ Π² extract_json_payload")
|
159 |
+
return json_candidate
|
160 |
+
except json.JSONDecodeError as e:
|
161 |
+
logger.warning(f"ΠΠ·Π²Π»Π΅ΡΠ΅Π½Π½ΡΠΉ JSON Π½Π΅Π²Π°Π»ΠΈΠ΄Π½ΡΠΉ: {str(e)}")
|
162 |
+
logger.debug(f"ΠΡΠΎΠ±Π»Π΅ΠΌΠ½ΡΠΉ JSON: {json_candidate}")
|
163 |
+
# ΠΡΡΠ°Π΅ΠΌΡΡ Π²Π΅ΡΠ½ΡΡΡ ΠΏΠΎΠ»Π½ΡΡ ΡΡΡΠΎΠΊΡ Π±Π΅Π· ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ
|
164 |
+
try:
|
165 |
+
# ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ Π²ΡΡ ΡΡΡΠΎΠΊΠ° ΡΠΆΠ΅ ΡΠ²Π»ΡΠ΅ΡΡΡ Π²Π°Π»ΠΈΠ΄Π½ΡΠΌ JSON
|
166 |
+
json.loads(raw)
|
167 |
+
logger.debug("ΠΠΎΠ»Π½Π°Ρ ΡΡΡΠΎΠΊΠ° ΡΠ²Π»ΡΠ΅ΡΡΡ Π²Π°Π»ΠΈΠ΄Π½ΡΠΌ JSON")
|
168 |
+
return raw
|
169 |
+
except json.JSONDecodeError:
|
170 |
+
logger.warning("ΠΠ°ΠΆΠ΅ ΠΏΠΎΠ»Π½Π°Ρ ΡΡΡΠΎΠΊΠ° Π½Π΅ ΡΠ²Π»ΡΠ΅ΡΡΡ Π²Π°Π»ΠΈΠ΄Π½ΡΠΌ JSON, Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡΡ")
|
171 |
+
return raw
|
172 |
+
|
173 |
+
# ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ Π΄Π»Ρ ΠΏΠ΅ΡΠ΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΡΡΠΈΠΉ
|
174 |
+
MCP_WRAPPERS = {}
|
175 |
+
|
176 |
+
# ΠΠ°Π³ΡΡΠΆΠ°Π΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ ΠΈΠ· .env ΡΠ°ΠΉΠ»Π°
|
177 |
+
load_dotenv()
|
178 |
+
api_key = os.getenv("NEBIUS_API_KEY")
|
179 |
+
if not api_key:
|
180 |
+
raise ValueError("NEBIUS_API_KEY not found in .env file")
|
181 |
+
|
182 |
+
# ΠΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ MCP ΡΠ΅ΡΠ²Π΅ΡΠΎΠ² (Π΄Π»Ρ Docker Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΌΠΈ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ)
|
183 |
+
BANDIT_PORT = os.getenv('BANDIT_INTERNAL_PORT', '7861')
|
184 |
+
DETECT_SECRETS_PORT = os.getenv('DETECT_SECRETS_INTERNAL_PORT', '7862')
|
185 |
+
PIP_AUDIT_PORT = os.getenv('PIP_AUDIT_INTERNAL_PORT', '7863')
|
186 |
+
CIRCLE_TEST_PORT = os.getenv('CIRCLE_TEST_INTERNAL_PORT', '7864')
|
187 |
+
SEMGREP_PORT = os.getenv('SEMGREP_INTERNAL_PORT', '7865')
|
188 |
+
|
189 |
+
MCP_SERVERS = {
|
190 |
+
"bandit": {
|
191 |
+
"url": f"http://bandit-security-scanner:{BANDIT_PORT}/gradio_api/mcp/sse",
|
192 |
+
"description": "Python code security analysis",
|
193 |
+
"port": int(BANDIT_PORT)
|
194 |
+
},
|
195 |
+
"detect_secrets": {
|
196 |
+
"url": f"http://detect-secrets-scanner:{DETECT_SECRETS_PORT}/gradio_api/mcp/sse",
|
197 |
+
"description": "Secret detection in code",
|
198 |
+
"port": int(DETECT_SECRETS_PORT)
|
199 |
+
},
|
200 |
+
"pip_audit": {
|
201 |
+
"url": f"http://pip-audit-scanner:{PIP_AUDIT_PORT}/gradio_api/mcp/sse",
|
202 |
+
"description": "Python package vulnerability scanning",
|
203 |
+
"port": int(PIP_AUDIT_PORT)
|
204 |
+
},
|
205 |
+
"circle_test": {
|
206 |
+
"url": f"http://circle-test-scanner:{CIRCLE_TEST_PORT}/gradio_api/mcp/sse",
|
207 |
+
"description": "Security policy compliance checking",
|
208 |
+
"port": int(CIRCLE_TEST_PORT)
|
209 |
+
},
|
210 |
+
"semgrep": {
|
211 |
+
"url": f"http://semgrep-scanner:{SEMGREP_PORT}/gradio_api/mcp/sse",
|
212 |
+
"description": "Advanced static code analysis",
|
213 |
+
"port": int(SEMGREP_PORT)
|
214 |
+
}
|
215 |
+
}
|
216 |
+
|
217 |
+
def check_port(port: int) -> bool:
|
218 |
+
"""ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ Π΄ΠΎΡΡΡΠΏΠ½ΠΎΡΡΡ ΠΏΠΎΡΡΠ°"""
|
219 |
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
220 |
+
try:
|
221 |
+
result = sock.connect_ex(('127.0.0.1', port))
|
222 |
+
return result == 0
|
223 |
+
finally:
|
224 |
+
sock.close()
|
225 |
+
|
226 |
+
def signal_handler(signum, frame):
|
227 |
+
"""ΠΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊ ΡΠΈΠ³Π½Π°Π»ΠΎΠ² Π΄Π»Ρ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΠ³ΠΎ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ"""
|
228 |
+
logger.info("ΠΠΎΠ»ΡΡΠ΅Π½ ΡΠΈΠ³Π½Π°Π» Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ, Π·Π°ΠΊΡΡΠ²Π°Π΅ΠΌ ΡΠ΅ΡΠ²Π΅ΡΡ...")
|
229 |
+
sys.exit(0)
|
230 |
+
|
231 |
+
# Π Π΅Π³ΠΈΡΡΡΠΈΡΡΠ΅ΠΌ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠΈ ΡΠΈΠ³Π½Π°Π»ΠΎΠ²
|
232 |
+
signal.signal(signal.SIGINT, signal_handler)
|
233 |
+
signal.signal(signal.SIGTERM, signal_handler)
|
234 |
+
|
235 |
+
def generate_simple_diff(original_content: str, updated_content: str, file_path: str) -> str:
|
236 |
+
"""
|
237 |
+
ΠΠ΅Π½Π΅ΡΠΈΡΡΠ΅Ρ ΠΏΡΠΎΡΡΠΎΠΉ diff ΠΌΠ΅ΠΆΠ΄Ρ ΠΎΡΠΈΠ³ΠΈΠ½Π°Π»ΡΠ½ΡΠΌ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½Π½ΡΠΌ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΡΠΌ
|
238 |
+
"""
|
239 |
+
diff_lines = list(difflib.unified_diff(
|
240 |
+
original_content.splitlines(keepends=True),
|
241 |
+
updated_content.splitlines(keepends=True),
|
242 |
+
fromfile=f"{file_path} (original)",
|
243 |
+
tofile=f"{file_path} (modified)",
|
244 |
+
n=3
|
245 |
+
))
|
246 |
+
if not diff_lines:
|
247 |
+
return "No changes detected."
|
248 |
+
added_lines = sum(1 for l in diff_lines if l.startswith("+") and not l.startswith("+++"))
|
249 |
+
removed_lines = sum(1 for l in diff_lines if l.startswith("-") and not l.startswith("---"))
|
250 |
+
diff_content = "".join(diff_lines)
|
251 |
+
stats = f"\nπ Changes: +{added_lines} additions, -{removed_lines} deletions"
|
252 |
+
return diff_content + stats
|
253 |
+
|
254 |
+
async def check_server_availability(url: str, max_retries: int = 5, delay: float = 5.0) -> bool:
|
255 |
+
"""ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ Π΄ΠΎΡΡΡΠΏΠ½ΠΎΡΡΡ MCP ΡΠ΅ΡΠ²Π΅ΡΠ° Ρ ΡΠ²Π΅Π»ΠΈΡΠ΅Π½Π½ΡΠΌΠΈ ΡΠ°ΠΉΠΌΠ°ΡΡΠ°ΠΌΠΈ"""
|
256 |
+
logger.debug(f"ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π΄ΠΎΡΡΡΠΏΠ½ΠΎΡΡΠΈ ΡΠ΅ΡΠ²Π΅ΡΠ°: {url}")
|
257 |
+
for i in range(max_retries):
|
258 |
+
try:
|
259 |
+
async with aiohttp.ClientSession() as session:
|
260 |
+
async with session.get(url, timeout=30) as response:
|
261 |
+
if response.status == 200:
|
262 |
+
logger.info(f"Π‘Π΅ΡΠ²Π΅Ρ {url} Π΄ΠΎΡΡΡΠΏΠ΅Π½")
|
263 |
+
return True
|
264 |
+
except Exception as e:
|
265 |
+
logger.warning(f"ΠΠΎΠΏΡΡΠΊΠ° {i+1}/{max_retries} Π½Π΅ ΡΠ΄Π°Π»Π°ΡΡ: {str(e)}")
|
266 |
+
await asyncio.sleep(delay)
|
267 |
+
logger.error(f"Π‘Π΅ΡΠ²Π΅Ρ {url} Π½Π΅Π΄ΠΎΡΡΡΠΏΠ΅Π½ ΠΏΠΎΡΠ»Π΅ {max_retries} ΠΏΠΎΠΏΡΡΠΎΠΊ")
|
268 |
+
return False
|
269 |
+
|
270 |
+
async def init_all_tools():
|
271 |
+
"""ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅Ρ Π²ΡΠ΅ MCP ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π· ΠΏΡΠΈ ΡΡΠ°ΡΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ"""
|
272 |
+
global MCP_WRAPPERS
|
273 |
+
|
274 |
+
try:
|
275 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ SSE ΠΊΠ»ΠΈΠ΅Π½ΡΡ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΡΠ΅ΡΠ²Π΅ΡΠ°
|
276 |
+
for name, cfg in MCP_SERVERS.items():
|
277 |
+
async with sse_client(cfg["url"]) as (read, write):
|
278 |
+
async with ClientSession(read, write) as session:
|
279 |
+
MCP_WRAPPERS[name] = MCPTools(session=session)
|
280 |
+
|
281 |
+
logger.info("ΠΡΠ΅ MCP ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ ΡΡΠΏΠ΅ΡΠ½ΠΎ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Ρ")
|
282 |
+
except Exception as e:
|
283 |
+
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° ΠΏΡΠΈ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ MCP ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠΎΠ²: {str(e)}")
|
284 |
+
raise
|
285 |
+
|
286 |
+
async def run_mcp_agent(message, server_name):
|
287 |
+
"""ΠΠ°ΠΏΡΡΠΊΠ°Π΅Ρ Π°Π³Π΅Π½ΡΠ° Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ MCP ΡΠ΅ΡΠ²Π΅ΡΠ°"""
|
288 |
+
logger.info(f"ΠΠ°ΠΏΡΡΠΊ MCP Π°Π³Π΅Π½ΡΠ° Π΄Π»Ρ {server_name}")
|
289 |
+
|
290 |
+
if not api_key:
|
291 |
+
logger.error("Nebius API key Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ Π² .env ΡΠ°ΠΉΠ»Π΅")
|
292 |
+
return "Error: Nebius API key not found in .env file"
|
293 |
+
|
294 |
+
if server_name not in MCP_SERVERS:
|
295 |
+
logger.error(f"ΠΠ΅ΠΈΠ·Π²Π΅ΡΡΠ½ΡΠΉ MCP ΡΠ΅ΡΠ²Π΅Ρ: {server_name}")
|
296 |
+
return f"Error: Unknown MCP server {server_name}"
|
297 |
+
|
298 |
+
if server_name not in MCP_WRAPPERS:
|
299 |
+
logger.error(f"MCP ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½Ρ {server_name} Π½Π΅ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°Π½")
|
300 |
+
return f"Error: MCP tool {server_name} not initialized"
|
301 |
+
|
302 |
+
try:
|
303 |
+
# ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½Ρ ΠΈΠ· ΠΊΡΡΠ°
|
304 |
+
mcp_tools = MCP_WRAPPERS[server_name]
|
305 |
+
|
306 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π°Π³Π΅Π½ΡΠ°
|
307 |
+
agent = Agent(
|
308 |
+
tools=[mcp_tools],
|
309 |
+
instructions=dedent(f"""\
|
310 |
+
You are an intelligent security assistant with access to MCP tools for {server_name}.
|
311 |
+
|
312 |
+
IMPORTANT INSTRUCTIONS:
|
313 |
+
1. Use the appropriate MCP tool to analyze the provided code
|
314 |
+
2. Return ONLY the raw JSON result from the MCP tool
|
315 |
+
3. Do NOT add any explanations, commentary, or additional formatting
|
316 |
+
4. Do NOT wrap the result in markdown code blocks
|
317 |
+
5. Do NOT add any text before or after the JSON
|
318 |
+
6. If the tool returns a "results" field, return the complete response including that field
|
319 |
+
|
320 |
+
The JSON output should be clean and parseable without any modifications.
|
321 |
+
"""),
|
322 |
+
markdown=False, # ΠΡΠΊΠ»ΡΡΠ°Π΅ΠΌ Markdown Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΡΠΈΡΡΠΎΠ³ΠΎ JSON
|
323 |
+
show_tool_calls=True,
|
324 |
+
model=Nebius(
|
325 |
+
id="Qwen/Qwen3-30B-A3B-fast",
|
326 |
+
api_key=api_key
|
327 |
+
)
|
328 |
+
)
|
329 |
+
|
330 |
+
# Π€ΠΎΡΠΌΠ°ΡΠΈΡΡΠ΅ΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅
|
331 |
+
formatted_message = f"Analyze this code using {server_name}: {message}"
|
332 |
+
|
333 |
+
# ΠΠ°ΠΏΡΡΠΊΠ°Π΅ΠΌ Π°Π½Π°Π»ΠΈΠ·
|
334 |
+
response = await agent.arun(formatted_message)
|
335 |
+
logger.info(f"Π£ΡΠΏΠ΅ΡΠ½ΠΎΠ΅ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ Π΄Π»Ρ {server_name}")
|
336 |
+
logger.debug(f"ΠΠΎΠ»Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ Π°Π³Π΅Π½ΡΠ° Π΄Π»Ρ {server_name}: {response.content}")
|
337 |
+
|
338 |
+
# Π‘Π½Π°ΡΠ°Π»Π° ΠΏΡΠΎΠ±ΡΠ΅ΠΌ Π°Π³ΡΠ΅ΡΡΠΈΠ²Π½ΡΡ ΠΎΡΠΈΡΡΠΊΡ
|
339 |
+
cleaned_response = clean_and_validate_json(response.content)
|
340 |
+
|
341 |
+
# ΠΡΠ»ΠΈ Π°Π³ΡΠ΅ΡΡΠΈΠ²Π½Π°Ρ ΠΎΡΠΈΡΡΠΊΠ° Π½Π΅ ΠΏΠΎΠΌΠΎΠ³Π»Π°, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΎΠ±ΡΡΠ½ΡΡ ΡΡΠ½ΠΊΡΠΈΡ
|
342 |
+
if cleaned_response == response.content:
|
343 |
+
cleaned_response = extract_json_payload(response.content)
|
344 |
+
|
345 |
+
# Π‘ΡΠ°Π½Π΄Π°ΡΡΠΈΠ·ΠΈΡΡΠ΅ΠΌ ΠΎΡΠ²Π΅Ρ Π΄Π»Ρ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ ΡΠ΅ΡΠ²Π΅ΡΠ°
|
346 |
+
standardized_response = standardize_mcp_response(cleaned_response, server_name)
|
347 |
+
|
348 |
+
logger.debug(f"Π‘ΡΠ°Π½Π΄Π°ΡΡΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΎΡΠ²Π΅Ρ Π΄Π»Ρ {server_name}: {standardized_response}")
|
349 |
+
return standardized_response
|
350 |
+
|
351 |
+
except Exception as e:
|
352 |
+
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ {server_name}: {str(e)}")
|
353 |
+
# ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ΠΎΡΠΈΠ±ΠΊΡ Π² ΡΠΎΡΠΌΠ°ΡΠ΅ JSON Π΄Π»Ρ ΠΊΠΎΠ½ΡΠΈΡΡΠ΅Π½ΡΠ½ΠΎΡΡΠΈ
|
354 |
+
error_response = {
|
355 |
+
"success": False,
|
356 |
+
"error": f"Error running {server_name}: {str(e)}",
|
357 |
+
"results": {}
|
358 |
+
}
|
359 |
+
return json.dumps(error_response, ensure_ascii=False)
|
360 |
+
|
361 |
+
async def run_fix_agent(message):
|
362 |
+
"""ΠΠ°ΠΏΡΡΠΊΠ°Π΅Ρ Π°Π³Π΅Π½ΡΠ° Π΄Π»Ρ ΠΈΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΊΠΎΠ΄Π°"""
|
363 |
+
if not api_key:
|
364 |
+
return "Error: Nebius API key not found in .env file"
|
365 |
+
|
366 |
+
agent = Agent(
|
367 |
+
tools=[],
|
368 |
+
instructions=dedent("""\
|
369 |
+
You are an intelligent code refactoring assistant.
|
370 |
+
Based on the vulnerabilities detected, propose a corrected version of the code.
|
371 |
+
Return only the full updated source code, without any additional commentary or markup.
|
372 |
+
"""),
|
373 |
+
markdown=False,
|
374 |
+
show_tool_calls=False,
|
375 |
+
model=Nebius(
|
376 |
+
id="Qwen/Qwen3-30B-A3B-fast",
|
377 |
+
api_key=api_key
|
378 |
+
)
|
379 |
+
)
|
380 |
+
try:
|
381 |
+
response = await agent.arun(message)
|
382 |
+
return response.content
|
383 |
+
except Exception as e:
|
384 |
+
return f"Error proposing fixes: {e}"
|
385 |
+
|
386 |
+
async def process_file(file_obj, custom_checks, selected_servers):
|
387 |
+
"""ΠΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΠ°ΠΉΠ» Ρ ΠΏΠΎΠΌΠΎΡΡΡ Π²ΡΠ±ΡΠ°Π½Π½ΡΡ
MCP ΡΠ΅ΡΠ²Π΅ΡΠΎΠ²"""
|
388 |
+
if not file_obj:
|
389 |
+
return "", "", ""
|
390 |
+
|
391 |
+
try:
|
392 |
+
# Π‘ΠΎΡ
ΡΠ°Π½ΡΠ΅ΠΌ Π·Π°Π³ΡΡΠΆΠ΅Π½Π½ΡΠΉ ΡΠ°ΠΉΠ»
|
393 |
+
temp_dir = tempfile.gettempdir()
|
394 |
+
file_path = os.path.join(temp_dir, file_obj.name)
|
395 |
+
|
396 |
+
# ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ ΡΠ°ΠΉΠ»Π° ΠΈΠ· ΠΎΠ±ΡΠ΅ΠΊΡΠ° Gradio
|
397 |
+
with open(file_obj.name, 'r', encoding='utf-8') as f:
|
398 |
+
file_content = f.read()
|
399 |
+
|
400 |
+
with open(file_path, "w", encoding='utf-8') as f:
|
401 |
+
f.write(file_content)
|
402 |
+
|
403 |
+
# ΠΠΎΠ΄Π³ΠΎΡΠ°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ Π΄Π»Ρ Π²ΡΠ΅Ρ
ΡΠ΅ΡΠ²Π΅ΡΠΎΠ²
|
404 |
+
if custom_checks:
|
405 |
+
user_message = (
|
406 |
+
f"Please analyze this code for {custom_checks}, "
|
407 |
+
f"using the most comprehensive settings available:\n\n{file_content}"
|
408 |
+
)
|
409 |
+
else:
|
410 |
+
user_message = (
|
411 |
+
f"Please perform a full vulnerability and security analysis on this code, "
|
412 |
+
f"selecting the highest intensity settings:\n\n{file_content}"
|
413 |
+
)
|
414 |
+
|
415 |
+
# ΠΠ°ΠΏΡΡΠΊΠ°Π΅ΠΌ Π²ΡΠ΅ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΡ ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΠΎ
|
416 |
+
tasks = {
|
417 |
+
server: asyncio.create_task(run_mcp_agent(user_message, server))
|
418 |
+
for server in selected_servers
|
419 |
+
}
|
420 |
+
|
421 |
+
# Π‘ΠΎΠ±ΠΈΡΠ°Π΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΡ
|
422 |
+
raw_outputs = {
|
423 |
+
server: re.sub(r"<think>.*?</think>", "", await task, flags=re.DOTALL).strip()
|
424 |
+
for server, task in tasks.items()
|
425 |
+
}
|
426 |
+
|
427 |
+
# ΠΡΠ΅ΠΎΠ±ΡΠ°Π·ΡΠ΅ΠΌ raw_outputs Π² ΡΠΈΡΠ°Π΅ΠΌΡΠΉ ΡΠ΅ΠΊΡΡ Π΄Π»Ρ Markdown
|
428 |
+
formatted_results = []
|
429 |
+
for name, raw in raw_outputs.items():
|
430 |
+
logger.debug(f"ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ° Π΄Π»Ρ {name}, Π΄Π»ΠΈΠ½Π°: {len(raw)}")
|
431 |
+
logger.debug(f"ΠΠΎΠ»Π½ΠΎΠ΅ ΡΠΎΠ΄Π΅ΡΠΆΠΈΠΌΠΎΠ΅ Π΄Π»Ρ {name}: {raw}")
|
432 |
+
|
433 |
+
try:
|
434 |
+
# ΠΡΡΠ°Π΅ΠΌΡΡ ΡΠ°ΡΠΏΠ°ΡΡΠΈΡΡ JSON
|
435 |
+
parsed_data = json.loads(raw)
|
436 |
+
logger.debug(f"JSON ΡΡΠΏΠ΅ΡΠ½ΠΎ ΡΠ°ΡΠΏΠ°ΡΡΠ΅Π½ Π΄Π»Ρ {name}")
|
437 |
+
|
438 |
+
# ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΡ Π΅ΡΠ»ΠΈ ΠΎΠ½ΠΈ Π΅ΡΡΡ
|
439 |
+
if isinstance(parsed_data, dict) and 'results' in parsed_data:
|
440 |
+
display_data = parsed_data['results']
|
441 |
+
logger.debug(f"ΠΠ·Π²Π»Π΅ΡΠ΅Π½Ρ results Π΄Π»Ρ {name}")
|
442 |
+
else:
|
443 |
+
display_data = parsed_data
|
444 |
+
logger.debug(f"ΠΡΠΏΠΎΠ»ΡΠ·ΡΡΡΡΡ ΡΡΡΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π΄Π»Ρ {name}")
|
445 |
+
|
446 |
+
# Π€ΠΎΡΠΌΠ°ΡΠΈΡΡΠ΅ΠΌ Π΄Π»Ρ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ
|
447 |
+
formatted_json = json.dumps(display_data, indent=2, ensure_ascii=False)
|
448 |
+
formatted_results.append(f"### {name.upper()}:\n```json\n{formatted_json}\n```")
|
449 |
+
|
450 |
+
except json.JSONDecodeError as e:
|
451 |
+
# ΠΡΠ»ΠΈ JSON Π½Π΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΡΠΉ, ΠΏΠΎΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ ΠΊΠ°ΠΊ Π΅ΡΡΡ
|
452 |
+
logger.warning(f"ΠΠ΅ ΡΠ΄Π°Π»ΠΎΡΡ ΡΠ°ΡΠΏΠ°ΡΡΠΈΡΡ JSON Π΄Π»Ρ {name}: {str(e)}")
|
453 |
+
logger.debug(f"ΠΠΎΠ·ΠΈΡΠΈΡ ΠΎΡΠΈΠ±ΠΊΠΈ: {getattr(e, 'pos', 'Π½Π΅ΠΈΠ·Π²Π΅ΡΡΠ½ΠΎ')}")
|
454 |
+
logger.debug(f"ΠΠ»ΠΈΠ½Π° ΡΡΡΠΎΠΊΠΈ: {len(raw)}")
|
455 |
+
|
456 |
+
# ΠΡΡΠ°Π΅ΠΌΡΡ Π½Π°ΠΉΡΠΈ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ½ΡΡ ΠΎΠ±Π»Π°ΡΡΡ
|
457 |
+
if hasattr(e, 'pos') and e.pos:
|
458 |
+
start_pos = max(0, e.pos - 50)
|
459 |
+
end_pos = min(len(raw), e.pos + 50)
|
460 |
+
problem_area = raw[start_pos:end_pos]
|
461 |
+
logger.debug(f"ΠΡΠΎΠ±Π»Π΅ΠΌΠ½Π°Ρ ΠΎΠ±Π»Π°ΡΡΡ Π²ΠΎΠΊΡΡΠ³ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ {e.pos}: {repr(problem_area)}")
|
462 |
+
|
463 |
+
# ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ Π½Π° Π½Π°Π»ΠΈΡΠΈΠ΅ ΡΠΊΡΡΡΡΡ
ΡΠΈΠΌΠ²ΠΎΠ»ΠΎΠ²
|
464 |
+
has_non_printable = any(ord(c) < 32 and c not in '\n\r\t' for c in raw)
|
465 |
+
if has_non_printable:
|
466 |
+
logger.warning(f"ΠΠ±Π½Π°ΡΡΠΆΠ΅Π½Ρ Π½Π΅ΠΏΠ΅ΡΠ°ΡΠ°Π΅ΠΌΡΠ΅ ΡΠΈΠΌΠ²ΠΎΠ»Ρ Π² ΠΎΡΠ²Π΅ΡΠ΅ Π΄Π»Ρ {name}")
|
467 |
+
|
468 |
+
formatted_results.append(f"### {name.upper()} (Raw output):\n```\n{raw}\n```")
|
469 |
+
except Exception as e:
|
470 |
+
# ΠΡΠ±ΡΠ΅ Π΄ΡΡΠ³ΠΈΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ
|
471 |
+
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ° Π΄Π»Ρ {name}: {str(e)}")
|
472 |
+
formatted_results.append(f"### {name.upper()} (Error):\n```\nΠΡΠΈΠ±ΠΊΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ: {str(e)}\n```")
|
473 |
+
|
474 |
+
markdown_output = "\n\n".join(formatted_results)
|
475 |
+
|
476 |
+
# Π§ΠΈΡΠ°Π΅ΠΌ ΠΎΡΠΈΠ³ΠΈΠ½Π°Π»ΡΠ½ΡΠΉ ΠΊΠΎΠ΄
|
477 |
+
with open(file_path, 'r', encoding='utf-8') as f_in:
|
478 |
+
orig_code = f_in.read()
|
479 |
+
|
480 |
+
# ΠΠΎΠ΄Π³ΠΎΡΠ°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΠΏΡΠΎΠΌΠΏΡ Π΄Π»Ρ ΠΈΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠΉ
|
481 |
+
orig_name = os.path.basename(file_path)
|
482 |
+
fix_prompt = f"""Below is the full source code of '{orig_name}':
|
483 |
+
```python
|
484 |
+
{orig_code}
|
485 |
+
```
|
486 |
+
Please generate a corrected version of this code, addressing all security vulnerabilities. Return only the full updated source code."""
|
487 |
+
|
488 |
+
# ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΡΠΏΡΠ°Π²Π»Π΅Π½Π½ΡΠΉ ΠΊΠΎΠ΄
|
489 |
+
fixed_code = await run_fix_agent(fix_prompt)
|
490 |
+
|
491 |
+
# ΠΡΠΈΡΠ°Π΅ΠΌ ΠΊΠΎΠ΄ ΠΎΡ Π±Π»ΠΎΠΊΠΎΠ² <think>
|
492 |
+
cleaned_code = re.sub(r"<think>.*?</think>", "", fixed_code, flags=re.DOTALL).strip()
|
493 |
+
|
494 |
+
# ΠΠ΅Π½Π΅ΡΠΈΡΡΠ΅ΠΌ diff
|
495 |
+
diff_text = generate_simple_diff(orig_code, cleaned_code, orig_name)
|
496 |
+
|
497 |
+
return markdown_output, diff_text, cleaned_code
|
498 |
+
|
499 |
+
except Exception as e:
|
500 |
+
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° ΠΏΡΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ΅ ΡΠ°ΠΉΠ»Π°: {str(e)}")
|
501 |
+
return f"β ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ°: {str(e)}", "", ""
|
502 |
+
|
503 |
+
async def check_all_servers():
|
504 |
+
"""ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ Π΄ΠΎΡΡΡΠΏΠ½ΠΎΡΡΡ Π²ΡΠ΅Ρ
MCP ΡΠ΅ΡΠ²Π΅ΡΠΎΠ²"""
|
505 |
+
unavailable_servers = []
|
506 |
+
for server_name, config in MCP_SERVERS.items():
|
507 |
+
if not check_port(config["port"]):
|
508 |
+
unavailable_servers.append(f"{server_name} (ΠΏΠΎΡΡ {config['port']})")
|
509 |
+
return unavailable_servers
|
510 |
+
|
511 |
+
def process_file_sync(file_obj, custom_checks, selected_servers):
|
512 |
+
"""Π‘ΠΈΠ½Ρ
ΡΠΎΠ½Π½Π°Ρ ΠΎΠ±Π΅ΡΡΠΊΠ° Π΄Π»Ρ process_file"""
|
513 |
+
return asyncio.run(process_file(file_obj, custom_checks, selected_servers))
|
514 |
+
|
515 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ Gradio
|
516 |
+
with gr.Blocks(title="Security Tools MCP Agent") as demo:
|
517 |
+
gr.Markdown("# π Security Tools MCP Agent")
|
518 |
+
|
519 |
+
with gr.Row():
|
520 |
+
with gr.Column(scale=1):
|
521 |
+
file_input = gr.File(
|
522 |
+
label="Upload a code file",
|
523 |
+
file_types=[".py", ".js", ".java", ".go", ".rb"]
|
524 |
+
)
|
525 |
+
custom_checks = gr.Textbox(
|
526 |
+
label="Enter specific checks or tools to use (optional)",
|
527 |
+
placeholder="e.g., SQL injection, shell injection, detect secrets"
|
528 |
+
)
|
529 |
+
server_checkboxes = gr.CheckboxGroup(
|
530 |
+
choices=list(MCP_SERVERS.keys()),
|
531 |
+
value=list(MCP_SERVERS.keys()),
|
532 |
+
label="Select MCP Servers"
|
533 |
+
)
|
534 |
+
scan_button = gr.Button("Run Scan", variant="primary")
|
535 |
+
|
536 |
+
with gr.Row():
|
537 |
+
with gr.Column(scale=1):
|
538 |
+
analysis_output = gr.Markdown(label="Security Analysis Results")
|
539 |
+
diff_output = gr.Textbox(label="Proposed Code Fixes", lines=10)
|
540 |
+
fixed_code_output = gr.Code(label="Fixed Code", language="python")
|
541 |
+
download_button = gr.File(label="Download corrected file")
|
542 |
+
|
543 |
+
def update_download_button(fixed_code):
|
544 |
+
if fixed_code:
|
545 |
+
temp_dir = tempfile.gettempdir()
|
546 |
+
fixed_path = os.path.join(temp_dir, "fixed_code.py")
|
547 |
+
with open(fixed_path, "w") as f:
|
548 |
+
f.write(fixed_code)
|
549 |
+
return fixed_path
|
550 |
+
return None
|
551 |
+
|
552 |
+
scan_button.click(
|
553 |
+
fn=process_file_sync,
|
554 |
+
inputs=[file_input, custom_checks, server_checkboxes],
|
555 |
+
outputs=[analysis_output, diff_output, fixed_code_output]
|
556 |
+
).then(
|
557 |
+
fn=update_download_button,
|
558 |
+
inputs=[fixed_code_output],
|
559 |
+
outputs=[download_button]
|
560 |
+
)
|
561 |
+
|
562 |
+
if __name__ == "__main__":
|
563 |
+
try:
|
564 |
+
# ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅ΠΌ Π²ΡΠ΅ MCP ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ ΠΏΡΠΈ ΡΡΠ°ΡΡΠ΅
|
565 |
+
asyncio.run(init_all_tools())
|
566 |
+
|
567 |
+
logger.info("ΠΠ°ΠΏΡΡΠΊ Security Tools MCP Agent...")
|
568 |
+
demo.launch(share=True)
|
569 |
+
except Exception as e:
|
570 |
+
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° Π·Π°ΠΏΡΡΠΊΠ° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ: {str(e)}")
|
571 |
+
sys.exit(1)
|
mcp.json
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"mcpServers": {
|
3 |
+
"bandit-security": {
|
4 |
+
"command": "npx",
|
5 |
+
"args": [
|
6 |
+
"-y",
|
7 |
+
"mcp-remote",
|
8 |
+
"http://localhost:7860/gradio_api/mcp/sse",
|
9 |
+
"--transport",
|
10 |
+
"sse-only"
|
11 |
+
]
|
12 |
+
},
|
13 |
+
"detect-secrets": {
|
14 |
+
"command": "npx",
|
15 |
+
"args": [
|
16 |
+
"-y",
|
17 |
+
"mcp-remote",
|
18 |
+
"http://localhost:7861/gradio_api/mcp/sse",
|
19 |
+
"--transport",
|
20 |
+
"sse-only"
|
21 |
+
]
|
22 |
+
},
|
23 |
+
"pip-audit": {
|
24 |
+
"command": "npx",
|
25 |
+
"args": [
|
26 |
+
"-y",
|
27 |
+
"mcp-remote",
|
28 |
+
"http://localhost:7862/gradio_api/mcp/sse",
|
29 |
+
"--transport",
|
30 |
+
"sse-only"
|
31 |
+
]
|
32 |
+
},
|
33 |
+
"circle-test": {
|
34 |
+
"command": "npx",
|
35 |
+
"args": [
|
36 |
+
"-y",
|
37 |
+
"mcp-remote",
|
38 |
+
"http://localhost:7863/gradio_api/mcp/sse",
|
39 |
+
"--transport",
|
40 |
+
"sse-only"
|
41 |
+
]
|
42 |
+
},
|
43 |
+
"semgrep": {
|
44 |
+
"command": "npx",
|
45 |
+
"args": [
|
46 |
+
"-y",
|
47 |
+
"mcp-remote",
|
48 |
+
"http://localhost:7864/gradio_api/mcp/sse",
|
49 |
+
"--transport",
|
50 |
+
"sse-only"
|
51 |
+
]
|
52 |
+
}
|
53 |
+
}
|
54 |
+
}
|
pip_audit_mcp.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
MCP server for pip-audit - a tool for scanning Python environments for known vulnerabilities
|
4 |
+
"""
|
5 |
+
|
6 |
+
import subprocess
|
7 |
+
import json
|
8 |
+
from typing import Dict
|
9 |
+
import gradio as gr
|
10 |
+
|
11 |
+
def pip_audit_scan() -> Dict:
|
12 |
+
"""
|
13 |
+
Scans Python environments for known vulnerabilities using pip-audit with basic settings.
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
Dict: Scan results
|
17 |
+
"""
|
18 |
+
try:
|
19 |
+
cmd = ["pip-audit", "--format", "json"]
|
20 |
+
|
21 |
+
print(f"Executing command: {' '.join(cmd)}")
|
22 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
23 |
+
stdout, stderr = result.stdout, result.stderr
|
24 |
+
return_code = result.returncode
|
25 |
+
|
26 |
+
if return_code != 0:
|
27 |
+
print(f"pip-audit command failed with return code {return_code}")
|
28 |
+
print(f"Stderr: {stderr}")
|
29 |
+
return {
|
30 |
+
"success": False,
|
31 |
+
"error": f"pip-audit command failed with return code {return_code}",
|
32 |
+
"stdout": stdout,
|
33 |
+
"stderr": stderr,
|
34 |
+
"return_code": return_code
|
35 |
+
}
|
36 |
+
|
37 |
+
try:
|
38 |
+
output_data = json.loads(stdout) if stdout else {}
|
39 |
+
return {
|
40 |
+
"success": True,
|
41 |
+
"results": output_data,
|
42 |
+
"stderr": stderr,
|
43 |
+
"return_code": return_code
|
44 |
+
}
|
45 |
+
except json.JSONDecodeError as e:
|
46 |
+
print(f"JSON parsing error: {e}")
|
47 |
+
print(f"Raw stdout: {stdout}")
|
48 |
+
return {
|
49 |
+
"success": False,
|
50 |
+
"error": "JSON parsing error: " + str(e),
|
51 |
+
"stdout": stdout,
|
52 |
+
"stderr": stderr,
|
53 |
+
"return_code": return_code
|
54 |
+
}
|
55 |
+
|
56 |
+
except Exception as e:
|
57 |
+
print(f"Error executing pip-audit: {str(e)}")
|
58 |
+
return {
|
59 |
+
"success": False,
|
60 |
+
"error": f"Error executing pip-audit: {str(e)}"
|
61 |
+
}
|
62 |
+
|
63 |
+
# Create Gradio interface
|
64 |
+
with gr.Blocks(title="Pip Audit MCP") as demo:
|
65 |
+
gr.Markdown("# π‘οΈ Pip Audit Scanner")
|
66 |
+
gr.Markdown("Vulnerability scanning tool for Python environments with MCP support")
|
67 |
+
|
68 |
+
with gr.Tab("Basic Scanning"):
|
69 |
+
scan_btn = gr.Button("π Run Basic Audit", variant="primary")
|
70 |
+
scan_output = gr.JSON(label="Audit Results")
|
71 |
+
|
72 |
+
scan_btn.click(
|
73 |
+
fn=pip_audit_scan,
|
74 |
+
inputs=[],
|
75 |
+
outputs=scan_output
|
76 |
+
)
|
77 |
+
|
78 |
+
if __name__ == "__main__":
|
79 |
+
demo.launch(mcp_server=True)
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio[mcp]
|
2 |
+
bandit[toml,baseline,sarif]
|
3 |
+
pathlib
|
4 |
+
smolagents
|
5 |
+
detect-secrets[word_list,gibberish]
|
6 |
+
pip-audit
|
7 |
+
python-dotenv
|
8 |
+
aiohttp
|
9 |
+
semgrep
|
semgrep_mcp.py
ADDED
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
MCP server for Semgrep - a tool for static analysis of code
|
4 |
+
"""
|
5 |
+
|
6 |
+
import gradio as gr
|
7 |
+
import subprocess
|
8 |
+
import json
|
9 |
+
import os
|
10 |
+
import tempfile
|
11 |
+
from typing import Dict, List, Optional
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
def semgrep_scan(
|
15 |
+
code_input: str,
|
16 |
+
scan_type: str = "code",
|
17 |
+
rules: str = "p/default",
|
18 |
+
output_format: str = "json"
|
19 |
+
) -> Dict:
|
20 |
+
"""
|
21 |
+
Π‘ΠΊΠ°Π½ΠΈΡΡΠ΅Ρ ΠΊΠΎΠ΄ Ρ ΠΏΠΎΠΌΠΎΡΡΡ Semgrep.
|
22 |
+
|
23 |
+
Args:
|
24 |
+
code_input (str): ΠΠΎΠ΄ Π΄Π»Ρ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈΠ»ΠΈ ΠΏΡΡΡ ΠΊ ΡΠ°ΠΉΠ»Ρ/Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
25 |
+
scan_type (str): Π’ΠΈΠΏ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ - 'code' Π΄Π»Ρ ΠΏΡΡΠΌΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π° ΠΈΠ»ΠΈ 'path' Π΄Π»Ρ ΡΠ°ΠΉΠ»Π°/Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ
|
26 |
+
rules (str): ΠΡΠ°Π²ΠΈΠ»Π° Π΄Π»Ρ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, 'p/default' ΠΈΠ»ΠΈ ΠΏΡΡΡ ΠΊ ΡΠ°ΠΉΠ»Ρ ΠΏΡΠ°Π²ΠΈΠ»)
|
27 |
+
output_format (str): Π€ΠΎΡΠΌΠ°Ρ Π²ΡΠ²ΠΎΠ΄Π° - 'json' ΠΈΠ»ΠΈ 'text'
|
28 |
+
|
29 |
+
Returns:
|
30 |
+
Dict: Π Π΅Π·ΡΠ»ΡΡΠ°ΡΡ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
|
31 |
+
"""
|
32 |
+
try:
|
33 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ ΡΠ°ΠΉΠ» ΠΈΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΡΡΡΠ΅ΡΡΠ²ΡΡΡΠΈΠΉ ΠΏΡΡΡ
|
34 |
+
if scan_type == "code":
|
35 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ ΡΠ°ΠΉΠ» Ρ ΠΊΠΎΠ΄ΠΎΠΌ
|
36 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp_file:
|
37 |
+
tmp_file.write(code_input)
|
38 |
+
target_path = tmp_file.name
|
39 |
+
else:
|
40 |
+
# ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΡΡΡΠ΅ΡΡΠ²ΡΡΡΠΈΠΉ ΠΏΡΡΡ
|
41 |
+
target_path = code_input
|
42 |
+
if not os.path.exists(target_path):
|
43 |
+
return {
|
44 |
+
"error": f"Path not found: {target_path}",
|
45 |
+
"success": False
|
46 |
+
}
|
47 |
+
|
48 |
+
# Π‘ΡΡΠΎΠΈΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Ρ semgrep
|
49 |
+
cmd = ["semgrep", "scan"]
|
50 |
+
|
51 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΏΡΠ°Π²ΠΈΠ»Π°
|
52 |
+
cmd.extend(["--config", rules])
|
53 |
+
|
54 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΡΠΎΡΠΌΠ°Ρ Π²ΡΠ²ΠΎΠ΄Π°
|
55 |
+
if output_format == "json":
|
56 |
+
cmd.extend(["--json"])
|
57 |
+
|
58 |
+
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΏΡΡΡ Π΄Π»Ρ ΡΠΊΠ°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ
|
59 |
+
cmd.append(target_path)
|
60 |
+
|
61 |
+
# ΠΡΠΏΠΎΠ»Π½ΡΠ΅ΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Ρ
|
62 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
63 |
+
|
64 |
+
# Π£Π΄Π°Π»ΡΠ΅ΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ ΡΠ°ΠΉΠ», Π΅ΡΠ»ΠΈ ΠΎΠ½ Π±ΡΠ» ΡΠΎΠ·Π΄Π°Π½
|
65 |
+
if scan_type == "code":
|
66 |
+
try:
|
67 |
+
os.unlink(target_path)
|
68 |
+
except:
|
69 |
+
pass
|
70 |
+
|
71 |
+
# ΠΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅ΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ
|
72 |
+
if output_format == "json":
|
73 |
+
try:
|
74 |
+
output_data = json.loads(result.stdout) if result.stdout else {}
|
75 |
+
return {
|
76 |
+
"success": True,
|
77 |
+
"results": output_data,
|
78 |
+
"stderr": result.stderr,
|
79 |
+
"return_code": result.returncode
|
80 |
+
}
|
81 |
+
except json.JSONDecodeError:
|
82 |
+
return {
|
83 |
+
"success": False,
|
84 |
+
"error": "JSON parsing error",
|
85 |
+
"stdout": result.stdout,
|
86 |
+
"stderr": result.stderr,
|
87 |
+
"return_code": result.returncode
|
88 |
+
}
|
89 |
+
else:
|
90 |
+
return {
|
91 |
+
"success": True,
|
92 |
+
"output": result.stdout,
|
93 |
+
"stderr": result.stderr,
|
94 |
+
"return_code": result.returncode
|
95 |
+
}
|
96 |
+
|
97 |
+
except Exception as e:
|
98 |
+
return {
|
99 |
+
"success": False,
|
100 |
+
"error": f"Error executing Semgrep: {str(e)}"
|
101 |
+
}
|
102 |
+
|
103 |
+
def semgrep_list_rules() -> Dict:
|
104 |
+
"""
|
105 |
+
ΠΠΎΠ»ΡΡΠ°Π΅Ρ ΡΠΏΠΈΡΠΎΠΊ Π΄ΠΎΡΡΡΠΏΠ½ΡΡ
ΠΏΡΠ°Π²ΠΈΠ» Semgrep.
|
106 |
+
|
107 |
+
Returns:
|
108 |
+
Dict: Π‘ΠΏΠΈΡΠΎΠΊ ΠΏΡΠ°Π²ΠΈΠ»
|
109 |
+
"""
|
110 |
+
try:
|
111 |
+
cmd = ["semgrep", "list-rules"]
|
112 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
113 |
+
|
114 |
+
if result.returncode == 0:
|
115 |
+
rules = []
|
116 |
+
for line in result.stdout.split('\n'):
|
117 |
+
if line.strip():
|
118 |
+
rules.append(line.strip())
|
119 |
+
return {
|
120 |
+
"success": True,
|
121 |
+
"rules": rules
|
122 |
+
}
|
123 |
+
else:
|
124 |
+
return {
|
125 |
+
"success": False,
|
126 |
+
"error": f"Error listing rules: {result.stderr}"
|
127 |
+
}
|
128 |
+
|
129 |
+
except Exception as e:
|
130 |
+
return {
|
131 |
+
"success": False,
|
132 |
+
"error": f"Error executing Semgrep: {str(e)}"
|
133 |
+
}
|
134 |
+
|
135 |
+
# Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Gradio ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ
|
136 |
+
with gr.Blocks(title="Semgrep MCP") as demo:
|
137 |
+
gr.Markdown("# π Semgrep Scanner")
|
138 |
+
gr.Markdown("Static analysis tool with MCP support")
|
139 |
+
|
140 |
+
with gr.Tab("Basic Scanning"):
|
141 |
+
with gr.Row():
|
142 |
+
with gr.Column():
|
143 |
+
scan_type = gr.Radio(
|
144 |
+
choices=["code", "path"],
|
145 |
+
value="code",
|
146 |
+
label="Scan Type"
|
147 |
+
)
|
148 |
+
code_input = gr.Textbox(
|
149 |
+
lines=10,
|
150 |
+
placeholder="Enter code or path to scan...",
|
151 |
+
label="Code or Path"
|
152 |
+
)
|
153 |
+
rules = gr.Textbox(
|
154 |
+
value="p/default",
|
155 |
+
label="Rules (e.g., p/default or path to rules file)"
|
156 |
+
)
|
157 |
+
output_format = gr.Dropdown(
|
158 |
+
choices=["json", "text"],
|
159 |
+
value="json",
|
160 |
+
label="Output Format"
|
161 |
+
)
|
162 |
+
scan_btn = gr.Button("π Scan", variant="primary")
|
163 |
+
|
164 |
+
with gr.Column():
|
165 |
+
scan_output = gr.JSON(label="Scan Results")
|
166 |
+
|
167 |
+
scan_btn.click(
|
168 |
+
fn=semgrep_scan,
|
169 |
+
inputs=[code_input, scan_type, rules, output_format],
|
170 |
+
outputs=scan_output
|
171 |
+
)
|
172 |
+
|
173 |
+
with gr.Tab("Available Rules"):
|
174 |
+
rules_btn = gr.Button("π List Rules", variant="secondary")
|
175 |
+
rules_output = gr.JSON(label="Available Rules")
|
176 |
+
|
177 |
+
rules_btn.click(
|
178 |
+
fn=semgrep_list_rules,
|
179 |
+
inputs=[],
|
180 |
+
outputs=rules_output
|
181 |
+
)
|
182 |
+
|
183 |
+
with gr.Tab("Examples"):
|
184 |
+
gr.Markdown("""
|
185 |
+
## π¨ Examples of code to scan:
|
186 |
+
|
187 |
+
### 1. SQL Injection
|
188 |
+
```python
|
189 |
+
def get_user(user_id):
|
190 |
+
query = f"SELECT * FROM users WHERE id = {user_id}"
|
191 |
+
return db.execute(query)
|
192 |
+
```
|
193 |
+
|
194 |
+
### 2. Command Injection
|
195 |
+
```python
|
196 |
+
import subprocess
|
197 |
+
def run_command(command):
|
198 |
+
subprocess.call(f"ls {command}", shell=True)
|
199 |
+
```
|
200 |
+
|
201 |
+
### 3. Path Traversal
|
202 |
+
```python
|
203 |
+
def read_file(filename):
|
204 |
+
with open(f"/home/user/{filename}", "r") as f:
|
205 |
+
return f.read()
|
206 |
+
```
|
207 |
+
""")
|
208 |
+
|
209 |
+
if __name__ == "__main__":
|
210 |
+
demo.launch(mcp_server=True)
|
test_client.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Example MCP client for testing Bandit Security Scanner
|
4 |
+
"""
|
5 |
+
|
6 |
+
import os
|
7 |
+
import asyncio
|
8 |
+
from smolagents.mcp_client import MCPClient
|
9 |
+
|
10 |
+
async def test_bandit_mcp_client():
|
11 |
+
"""Tests connection to Bandit MCP server"""
|
12 |
+
|
13 |
+
# URL of your Bandit MCP server
|
14 |
+
server_url = "http://localhost:7860/gradio_api/mcp/sse"
|
15 |
+
|
16 |
+
print("π Connecting to Bandit MCP server...")
|
17 |
+
|
18 |
+
try:
|
19 |
+
async with MCPClient({"url": server_url}) as client:
|
20 |
+
# Get list of available tools
|
21 |
+
tools = await client.get_tools()
|
22 |
+
|
23 |
+
print(f"\nβ
Successfully connected! Available tools: {len(tools)}")
|
24 |
+
print("\nπ Available tools:")
|
25 |
+
for tool in tools:
|
26 |
+
print(f" β’ {tool.name}: {tool.description}")
|
27 |
+
|
28 |
+
# Test scanning vulnerable code
|
29 |
+
print("\nπ§ͺ Testing vulnerable code scanning...")
|
30 |
+
|
31 |
+
vulnerable_code = """
|
32 |
+
import subprocess
|
33 |
+
import pickle
|
34 |
+
|
35 |
+
# Vulnerabilities for testing
|
36 |
+
password = "hardcoded_secret123" # B105: Hardcoded password
|
37 |
+
eval("print('hello')") # B307: Use of eval
|
38 |
+
subprocess.call("ls -la", shell=True) # B602: subprocess with shell=True
|
39 |
+
data = pickle.loads(user_input) # B301: Pickle usage
|
40 |
+
"""
|
41 |
+
|
42 |
+
# Call bandit_scan
|
43 |
+
scan_tool = next((t for t in tools if t.name == "bandit_scan"), None)
|
44 |
+
if scan_tool:
|
45 |
+
result = await client.call_tool(
|
46 |
+
tool_name="bandit_scan",
|
47 |
+
arguments={
|
48 |
+
"code_input": vulnerable_code,
|
49 |
+
"scan_type": "code",
|
50 |
+
"severity_level": "low",
|
51 |
+
"confidence_level": "low",
|
52 |
+
"output_format": "json"
|
53 |
+
}
|
54 |
+
)
|
55 |
+
|
56 |
+
print("π Scan results:")
|
57 |
+
if result.get("success"):
|
58 |
+
issues = result.get("results", {}).get("results", [])
|
59 |
+
print(f" Found security issues: {len(issues)}")
|
60 |
+
|
61 |
+
for i, issue in enumerate(issues, 1):
|
62 |
+
print(f"\n π¨ Issue {i}:")
|
63 |
+
print(f" ID: {issue.get('test_id')}")
|
64 |
+
print(f" Severity: {issue.get('issue_severity')}")
|
65 |
+
print(f" Confidence: {issue.get('issue_confidence')}")
|
66 |
+
print(f" Description: {issue.get('issue_text')}")
|
67 |
+
print(f" Line: {issue.get('line_number')}")
|
68 |
+
print(f" Code: {issue.get('code', '').strip()}")
|
69 |
+
else:
|
70 |
+
print(f" β Scan error: {result.get('error')}")
|
71 |
+
else:
|
72 |
+
print(" β bandit_scan tool not found")
|
73 |
+
|
74 |
+
# Test baseline creation (if file exists)
|
75 |
+
print("\nπ― Testing baseline creation...")
|
76 |
+
baseline_tool = next((t for t in tools if t.name == "bandit_baseline"), None)
|
77 |
+
if baseline_tool:
|
78 |
+
# Create temporary file with code
|
79 |
+
import tempfile
|
80 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp_file:
|
81 |
+
tmp_file.write(vulnerable_code)
|
82 |
+
tmp_path = tmp_file.name
|
83 |
+
|
84 |
+
baseline_result = await client.call_tool(
|
85 |
+
tool_name="bandit_baseline",
|
86 |
+
arguments={
|
87 |
+
"target_path": tmp_path,
|
88 |
+
"baseline_file": "/tmp/bandit_baseline.json"
|
89 |
+
}
|
90 |
+
)
|
91 |
+
|
92 |
+
print("π Baseline result:")
|
93 |
+
if baseline_result.get("success"):
|
94 |
+
action = baseline_result.get("action", "unknown")
|
95 |
+
message = baseline_result.get("message", "")
|
96 |
+
print(f" β
Action: {action}")
|
97 |
+
if message:
|
98 |
+
print(f" π Message: {message}")
|
99 |
+
else:
|
100 |
+
print(f" β Baseline error: {baseline_result.get('error')}")
|
101 |
+
|
102 |
+
# Clean up temporary file
|
103 |
+
try:
|
104 |
+
os.unlink(tmp_path)
|
105 |
+
except:
|
106 |
+
pass
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
print(f"β Connection error: {e}")
|
110 |
+
print("π‘ Make sure Bandit MCP server is running on http://localhost:7860")
|
111 |
+
|
112 |
+
if __name__ == "__main__":
|
113 |
+
print("π Bandit MCP Client Test")
|
114 |
+
print("=" * 50)
|
115 |
+
asyncio.run(test_bandit_mcp_client())
|
test_dependencies.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Check installation of all required dependencies for Bandit MCP and Detect Secrets MCP
|
4 |
+
"""
|
5 |
+
|
6 |
+
import sys
|
7 |
+
import subprocess
|
8 |
+
|
9 |
+
def check_package(package_name, import_name=None):
|
10 |
+
"""Checks package installation"""
|
11 |
+
if import_name is None:
|
12 |
+
import_name = package_name
|
13 |
+
|
14 |
+
try:
|
15 |
+
__import__(import_name)
|
16 |
+
print(f"β
{package_name} - installed")
|
17 |
+
return True
|
18 |
+
except ImportError:
|
19 |
+
print(f"β {package_name} - NOT installed")
|
20 |
+
return False
|
21 |
+
|
22 |
+
def check_command(command):
|
23 |
+
"""Checks command availability in system"""
|
24 |
+
try:
|
25 |
+
result = subprocess.run([command, "--version"],
|
26 |
+
capture_output=True, text=True)
|
27 |
+
if result.returncode == 0:
|
28 |
+
print(f"β
{command} - available")
|
29 |
+
return True
|
30 |
+
else:
|
31 |
+
print(f"β {command} - unavailable")
|
32 |
+
return False
|
33 |
+
except FileNotFoundError:
|
34 |
+
print(f"β {command} - not found")
|
35 |
+
return False
|
36 |
+
|
37 |
+
def main():
|
38 |
+
print("π Checking MCP Dependencies")
|
39 |
+
print("=" * 50)
|
40 |
+
|
41 |
+
all_good = True
|
42 |
+
|
43 |
+
# Check Python packages
|
44 |
+
print("\nπ¦ Python packages:")
|
45 |
+
packages = [
|
46 |
+
("gradio", "gradio"),
|
47 |
+
("bandit", "bandit"),
|
48 |
+
("smolagents", "smolagents"),
|
49 |
+
("detect_secrets", "detect_secrets")
|
50 |
+
]
|
51 |
+
|
52 |
+
for package, import_name in packages:
|
53 |
+
if not check_package(package, import_name):
|
54 |
+
all_good = False
|
55 |
+
|
56 |
+
# Check commands
|
57 |
+
print("\nπ§ System commands:")
|
58 |
+
commands = ["bandit", "npx", "detect-secrets"]
|
59 |
+
|
60 |
+
for command in commands:
|
61 |
+
if not check_command(command):
|
62 |
+
all_good = False
|
63 |
+
|
64 |
+
# Check specific bandit capabilities
|
65 |
+
print("\nπ― Bandit capabilities:")
|
66 |
+
try:
|
67 |
+
result = subprocess.run(["bandit", "--help"],
|
68 |
+
capture_output=True, text=True)
|
69 |
+
if "-f json" in result.stdout:
|
70 |
+
print("β
JSON format - supported")
|
71 |
+
else:
|
72 |
+
print("β JSON format - not supported")
|
73 |
+
|
74 |
+
if "-b" in result.stdout:
|
75 |
+
print("β
Baseline - supported")
|
76 |
+
else:
|
77 |
+
print("β Baseline - not supported")
|
78 |
+
|
79 |
+
if "-p" in result.stdout:
|
80 |
+
print("β
Profiles - supported")
|
81 |
+
else:
|
82 |
+
print("β Profiles - not supported")
|
83 |
+
|
84 |
+
except Exception as e:
|
85 |
+
print(f"β Error checking Bandit: {e}")
|
86 |
+
all_good = False
|
87 |
+
|
88 |
+
# Check specific detect-secrets capabilities
|
89 |
+
print("\nπ Detect Secrets capabilities:")
|
90 |
+
try:
|
91 |
+
result = subprocess.run(["detect-secrets", "scan", "--help"],
|
92 |
+
capture_output=True, text=True)
|
93 |
+
if "--baseline" in result.stdout:
|
94 |
+
print("β
Baseline - supported")
|
95 |
+
else:
|
96 |
+
print("β Baseline - not supported")
|
97 |
+
|
98 |
+
if "--base64-limit" in result.stdout:
|
99 |
+
print("β
Base64 entropy - supported")
|
100 |
+
else:
|
101 |
+
print("β Base64 entropy - not supported")
|
102 |
+
|
103 |
+
if "--hex-limit" in result.stdout:
|
104 |
+
print("β
Hex entropy - supported")
|
105 |
+
else:
|
106 |
+
print("β Hex entropy - not supported")
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
print(f"β Error checking Detect Secrets: {e}")
|
110 |
+
all_good = False
|
111 |
+
|
112 |
+
print("\n" + "=" * 50)
|
113 |
+
if all_good:
|
114 |
+
print("π All dependencies are installed correctly!")
|
115 |
+
print("π‘ Now you can run:")
|
116 |
+
print(" - python app.py (for Bandit MCP)")
|
117 |
+
print(" - python detect_secrets_mcp.py (for Detect Secrets MCP)")
|
118 |
+
else:
|
119 |
+
print("β οΈ Some dependencies are missing.")
|
120 |
+
print("π‘ Install them with: pip install -r requirements.txt")
|
121 |
+
print("π‘ For npm dependencies: npm install -g npx")
|
122 |
+
|
123 |
+
return all_good
|
124 |
+
|
125 |
+
if __name__ == "__main__":
|
126 |
+
main()
|