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