zjkarina commited on
Commit
4801adf
Β·
verified Β·
1 Parent(s): 8a70158

Upload 21 files

Browse files
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()