humanizetech commited on
Commit
6440b1f
·
1 Parent(s): dbef9bf

feat: implement VAPT agent application with a Gradio UI, AI tutor, and dashboard, and update README.

Browse files
Files changed (10) hide show
  1. README.md +401 -1
  2. __init__.py +0 -0
  3. ai_tutor.py +466 -0
  4. app.py +476 -0
  5. config.py +99 -0
  6. dashboard_utils.py +317 -0
  7. prompt.py +213 -0
  8. requirements.txt +38 -0
  9. vapt_agent.py +343 -0
  10. vapt_styles.css +270 -0
README.md CHANGED
@@ -9,6 +9,406 @@ app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: AI-powered VAPT agent built with Claude, MCP, and Gradio.
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  pinned: false
10
  license: apache-2.0
11
  short_description: AI-powered VAPT agent built with Claude, MCP, and Gradio.
12
+ tags:
13
+ - mcp-in-action-track-enterprise
14
+ - mcp-in-action-track-consumer
15
+ - mcp-in-action-track-creative
16
  ---
17
 
18
+
19
+ # 🏆 VAPT Agent - Intelligent API Security Testing
20
+
21
+ > **MCP's 1st Birthday Hackathon Submission** 🎉
22
+ > *Hosted by Anthropic & Gradio on Hugging Face*
23
+ > [🔗 Hackathon Page](https://huggingface.co/MCP-1st-Birthday)
24
+
25
+ LinkedIn post - [Refer HERE](https://www.linkedin.com/posts/chsubhasis_vapt-agent-activity-7399454144895467520-yR6I?utm_source=share&utm_medium=member_desktop&rcm=ACoAABIj1NcBQpSiJ5ZDC9YQBBsmL4fDfy7D0LU) || Demo Video - [Refer HERE](https://youtu.be/wFgW_o48pw8?si=2lpag5I4zsUz8J2d)
26
+ ---
27
+
28
+ ## 📋 Project Overview
29
+
30
+ **VAPT Agent** is an autonomous, AI-powered **Vulnerability Assessment and Penetration Testing (VAPT)** platform that revolutionizes API security testing. By combining **Anthropic's Claude Agent SDK**, **Postman MCP Server**, **Gradio Web Interface**, and **RAG-based security education**, this project showcases the power of Model Context Protocol (MCP) for building intelligent, context-aware security tools.
31
+
32
+ ### 🎯 What Makes This Special?
33
+
34
+ This project demonstrates **three powerful MCP integrations**:
35
+
36
+ 1. **🤖 Anthropic Claude SDK** - Powers the core VAPT reasoning agent with Claude Haiku 4.5
37
+ 2. **📮 Postman MCP Server** - Enables automatic API discovery and OpenAPI specification generation
38
+ 3. **🛠️ Custom VAPT MCP Server** - Provides specialized security testing tools (SQL injection, XSS, auth testing, etc.)
39
+
40
+ Combined with a **modern Gradio interface** and **RAG-powered AI tutor** using Chroma vector search, VAPT Agent bridges the gap between automated security testing and developer education.
41
+
42
+ ---
43
+
44
+ ## 🏗️ Architecture Overview
45
+
46
+ ```
47
+ ┌─────────────────────────────────────────────────────────────────┐
48
+ │ Gradio Web Interface │
49
+ │ (Real-time Progress, Visual Dashboard, AI Security Tutor) │
50
+ └────────────────────┬────────────────────────────────────────────┘
51
+
52
+
53
+ ┌─────────────────────────────────────────────────────────────────┐
54
+ │ VAPT Agent Orchestrator │
55
+ │ (vapt_agent.py) │
56
+ └─────┬──────────────────────────────┬────────────────────────────┘
57
+ │ │
58
+ ▼ ▼
59
+ ┌─────────────────────┐ ┌──────────────────────────────────────┐
60
+ │ Claude Agent SDK │ │ MCP Servers (via Claude SDK) │
61
+ │ (Haiku 4.5 Model) │◄───┤ ┌────────────┐ ┌────────────────┐ │
62
+ │ │ │ │ Postman │ │ Custom VAPT │ │
63
+ │ • Reasoning │ │ │ MCP Server │ │ MCP Tools │ │
64
+ │ • Test Planning │ │ │ (SSE) │ │ (Local Server) │ │
65
+ │ • Report Gen │ │ └────────────┘ └────────────────┘ │
66
+ └─────────────────────┘ └──────────────────────────────────────┘
67
+
68
+ ┌───────────────┴───────────────┐
69
+ ▼ ▼
70
+ ┌──────────────┐ ┌─────────────────────┐
71
+ │ Postman API │ │ Target API Endpoint │
72
+ │ • Discovery │ │ • Security Testing │
73
+ │ • Schema Gen │ │ • Vuln Detection │
74
+ └──────────────┘ └─────────────────────┘
75
+
76
+ ┌─────────────────────────────────────���───────────────────────────┐
77
+ │ AI Security Tutor (RAG) │
78
+ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
79
+ │ │ Nebius LLM │ │ Chroma DB │ │ Nebius Embeddings │ │
80
+ │ │ (gpt-oss-20b)│◄─┤ Vector Store │◄─┤ (Qwen3-Embed-8B) │ │
81
+ │ └──────────────┘ └──────────────┘ └────────────────────┘ │
82
+ │ ▲ ▲ │
83
+ │ │ │ │
84
+ │ └───────────────────┴─── VAPT Report Context │
85
+ └─────────────────────────────────────────────────────────────────┘
86
+ ```
87
+
88
+ ### 🔄 How It Works
89
+
90
+ 1. **User Input** → User provides API endpoint via Gradio interface
91
+ 2. **Discovery** → Claude agent uses **Postman MCP** to discover endpoints and generate OpenAPI spec
92
+ 3. **Testing** → Agent invokes **Custom VAPT MCP tools** to test for vulnerabilities
93
+ 4. **Reasoning** → **Claude Haiku 4.5** analyzes results and generates comprehensive security report
94
+ 5. **Visualization** → Gradio dashboard displays risk scores and severity charts
95
+ 6. **Education** → User asks questions → **AI Tutor** uses **RAG (Chroma + Nebius embeddings)** to retrieve relevant report sections → **Nebius LLM** generates educational explanations
96
+
97
+ ---
98
+
99
+ ## ✨ Key Features
100
+
101
+ ### 🔒 Comprehensive Security Testing
102
+
103
+ Automated vulnerability detection powered by Claude's reasoning and custom MCP tools:
104
+
105
+ - **Injection Attacks**: SQL injection, XSS, path traversal
106
+ - **Authentication & Authorization**: Broken auth detection, token validation
107
+ - **Rate Limiting**: DoS vulnerability assessment, burst testing (50 requests)
108
+ - **CORS Policy**: Origin validation, wildcard detection
109
+ - **Security Headers**: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, etc.
110
+
111
+ ### 🎨 Modern Gradio Web Interface
112
+
113
+ Beautiful, responsive UI built with Gradio featuring:
114
+
115
+ - **Real-time Progress Streaming** from Claude agent
116
+ - **Downloadable Markdown Reports** for audit trails
117
+ - **Visual Risk Dashboard** with interactive charts (risk gauge + severity pie chart)
118
+ - **Tabbed Interface** for organized information flow
119
+ - **Custom CSS Styling** for professional appearance
120
+
121
+ ### 🧠 RAG-Powered AI Security Tutor
122
+
123
+ **Context Engineering & Retrieval-Augmented Generation (RAG)** implementation:
124
+
125
+ #### How RAG Works in VAPT Agent:
126
+
127
+ 1. **Document Chunking** (`ai_tutor.py`):
128
+ - Report split into logical sections based on markdown headers (`##`)
129
+ - Large sections auto-chunked to ~2000 characters for optimal retrieval
130
+ - Preserves context boundaries for coherent answers
131
+
132
+ 2. **Vector Embedding** (Nebius + Chroma):
133
+ - Each chunk embedded using **Qwen3-Embedding-8B** (Nebius)
134
+ - Vectors stored in **Chroma** ephemeral in-memory database
135
+ - Index automatically rebuilt when report changes (SHA-256 content hashing)
136
+ - Never reuses old vectors for new reports
137
+
138
+ 3. **Semantic Search**:
139
+ - User question embedded with same model
140
+ - Top-K (default: 4) relevant chunks retrieved via cosine similarity
141
+ - Context passed to LLM for grounded responses
142
+
143
+ 4. **Context Engineering**:
144
+ - System prompt instructs LLM to prioritize retrieved VAPT report context
145
+ - Combines report snippets + optional web search (Tavily)
146
+ - Prevents hallucination by grounding answers in actual findings
147
+
148
+ **Benefits**:
149
+ - ✅ Accurate answers specific to YOUR security report
150
+ - ✅ No generic security advice - tailored to actual findings
151
+ - ✅ Efficient: Only relevant context sent to LLM (cost-effective)
152
+ - ✅ Educational: Explains vulnerabilities in your specific API
153
+
154
+ ### 📮 Postman MCP Integration
155
+
156
+ Leverages **Postman's official MCP server** (SSE protocol):
157
+
158
+ - Automatic API endpoint discovery
159
+ - OpenAPI/Swagger specification generation
160
+ - Request/response schema analysis
161
+ - Collection management for organized testing
162
+ - Seamless integration via Claude Agent SDK
163
+
164
+ ### 🤖 Anthropic Claude SDK
165
+
166
+ Core agent powered by **Claude Agent SDK**:
167
+
168
+ - **Model**: Claude Haiku 4.5 (fast, cost-efficient, high-quality reasoning)
169
+ - **Multi-turn Reasoning**: Agent conversations up to 100 turns
170
+ - **Tool Orchestration**: Coordinates Postman MCP + Custom VAPT MCP tools
171
+ - **Flexible Deployment**: Anthropic API or AWS Bedrock
172
+ - **Permission Mode**: Bypass permissions for automated testing
173
+
174
+ ---
175
+
176
+ ## 🎁 Benefits & Impact
177
+
178
+ ### For Security Professionals
179
+ - ⚡ **Save Time**: Automate repetitive VAPT tasks
180
+ - 📊 **Visual Insights**: Instantly understand risk posture with charts
181
+ - 🎓 **Learn On-the-Go**: AI tutor explains findings while you work
182
+ - 📄 **Audit-Ready Reports**: Comprehensive markdown reports with evidence
183
+
184
+ ### For Developers
185
+ - 🛡️ **Shift-Left Security**: Test APIs during development
186
+ - 📚 **Security Education**: Learn secure coding through AI tutor
187
+ - 🔧 **Easy Integration**: Simple API endpoint input
188
+ - 🚀 **Fast Feedback**: Get results in minutes, not days
189
+
190
+ ### For Organizations
191
+ - 💰 **Cost-Effective**: Reduce manual penetration testing costs
192
+ - 📈 **Scalable**: Test multiple APIs rapidly
193
+ - 📋 **Compliance**: Generate audit-ready security reports
194
+ - 🔄 **Continuous Testing**: Integrate into CI/CD pipelines
195
+
196
+ ### Technical Innovation
197
+ - 🧩 **MCP Showcase**: Demonstrates multiple MCP server integration
198
+ - 🔬 **RAG Best Practices**: Production-ready context engineering
199
+ - 🎨 **UX Excellence**: Beautiful, intuitive Gradio interface
200
+ - 🔓 **Open Source**: Extensible architecture for custom tools
201
+
202
+ ---
203
+
204
+ ## 🚀 Prerequisites
205
+
206
+ - **Python 3.10+**
207
+ - **[Postman API Key](https://postman.com/settings/api-keys)** - For MCP server access
208
+ - **[Anthropic API Key](https://console.anthropic.com/) OR AWS Bedrock** - For Claude Haiku 4.5
209
+ - **[Nebius API Key](https://nebius.com/)** - For AI Tutor (optional but recommended)
210
+
211
+ ---
212
+
213
+ ## 📦 Installation
214
+
215
+ 1. **Clone the repository**:
216
+ ```bash
217
+ git clone <repository-url>
218
+ cd vapt-agent
219
+ ```
220
+
221
+ 2. **Create virtual environment**:
222
+ ```bash
223
+ python -m venv venv
224
+ source venv/bin/activate # On Windows: venv\Scripts\activate
225
+ ```
226
+
227
+ 3. **Install dependencies**:
228
+ ```bash
229
+ pip install -r requirements.txt
230
+ ```
231
+
232
+ 4. **Configure environment**:
233
+ ```bash
234
+ cp .env.template .env
235
+ # Edit .env with your credentials
236
+ ```
237
+
238
+ ---
239
+
240
+ ## ⚙️ Configuration
241
+
242
+ Create a `.env` file with the following variables:
243
+
244
+ ```properties
245
+ # --- Core VAPT Agent Configuration ---
246
+
247
+ # AWS Bedrock (set to 1 to use Bedrock, 0 for Anthropic API)
248
+ CLAUDE_CODE_USE_BEDROCK=1
249
+
250
+ # AWS Credentials (if using Bedrock)
251
+ AWS_ACCESS_KEY_ID=your_access_key
252
+ AWS_SECRET_ACCESS_KEY=your_secret_key
253
+ AWS_REGION=us-east-1
254
+
255
+ # Model selection for VAPT Agent (Haiku 4.5 recommended)
256
+ ANTHROPIC_MODEL=global.anthropic.claude-haiku-4-5-20251001-v1:0
257
+ # If using Anthropic API directly:
258
+ # ANTHROPIC_API_KEY=sk-ant-...
259
+
260
+ # Postman API key (get from https://postman.com/settings/api-keys)
261
+ POSTMAN_API_KEY=your_postman_api_key
262
+
263
+ # --- AI Tutor Configuration (Nebius) ---
264
+
265
+ # Nebius API Key for Tutor and Embeddings
266
+ NEBIUS_API_KEY=your_nebius_api_key
267
+
268
+ # Nebius Base URL (optional, defaults to standard endpoint)
269
+ # NEBIUS_BASE_URL=https://api.tokenfactory.nebius.com/v1
270
+
271
+ # AI Tutor Chat Model
272
+ NEBIUS_TUTOR_MODEL=gpt-oss-20b
273
+
274
+ # Embedding Model for Vector Search (REQUIRED for RAG)
275
+ NEBIUS_EMBEDDING_MODEL=Qwen3-Embedding-8B
276
+
277
+ # --- Optional Web Search ---
278
+ # TAVILY_API_KEY=tvly-...
279
+ ```
280
+
281
+ ---
282
+
283
+ ## 🎮 Usage
284
+
285
+ ### 1. Web Interface (Recommended)
286
+
287
+ Launch the **Gradio dashboard** for an interactive experience:
288
+
289
+ ```bash
290
+ python app.py
291
+ ```
292
+
293
+ - Open your browser at `http://localhost:7861`
294
+ - Enter the API endpoint and HTTP method
295
+ - Watch the real-time progress log
296
+ - View the generated report, risk dashboard, and chat with the AI Security Tutor
297
+
298
+ ### 2. Command Line Interface
299
+
300
+ Run the agent directly from the terminal:
301
+
302
+ ```bash
303
+ python vapt_agent.py
304
+ ```
305
+
306
+ (Ensure `TEST_API_ENDPOINT` and `TEST_API_METHOD` are set in your `.env` file for CLI usage)
307
+
308
+ ---
309
+
310
+ ## 🔍 Security Tests Performed
311
+
312
+ The agent uses custom MCP tools (`vapt_tools.py`) to perform:
313
+
314
+ ### 1. **Injection Testing**
315
+ - SQL Injection with various payloads (e.g., `' OR '1'='1`)
316
+ - XSS (Cross-Site Scripting) detection
317
+ - Path traversal attempts (`../../../etc/passwd`)
318
+
319
+ ### 2. **Authentication Testing**
320
+ - Endpoint access without credentials
321
+ - Authentication bypass attempts
322
+ - Token validation and expiration checks
323
+
324
+ ### 3. **Rate Limiting**
325
+ - Burst request testing (50 rapid requests)
326
+ - 429 status code detection
327
+ - DoS vulnerability assessment
328
+
329
+ ### 4. **CORS Policy**
330
+ - Origin validation testing
331
+ - Wildcard (`*`) detection
332
+ - Cross-origin request testing
333
+
334
+ ### 5. **Security Headers**
335
+ - `Strict-Transport-Security` (HSTS)
336
+ - `X-Content-Type-Options`
337
+ - `X-Frame-Options`
338
+ - `Content-Security-Policy`
339
+ - `X-XSS-Protection`
340
+
341
+ ---
342
+
343
+ ## 📊 Output
344
+
345
+ The agent generates a comprehensive **Markdown report** saved as `vapt_report_YYYYMMDD_HHMMSS.md` containing:
346
+
347
+ - **Executive Summary** with risk score
348
+ - **Vulnerability Details** (Severity, Description, Evidence, Remediation)
349
+ - **Security Headers Analysis**
350
+ - **CORS Policy Review**
351
+ - **Rate Limiting Assessment**
352
+ - **Recommendations** for fixes
353
+
354
+ ---
355
+
356
+ ## 🛠️ Troubleshooting
357
+
358
+ ### Postman API Key Issues
359
+ - Get your API key from: https://postman.com/settings/api-keys
360
+ - Ensure the key has necessary permissions for collections and environments
361
+
362
+ ### AWS Bedrock Issues
363
+ - Verify AWS credentials are correct
364
+ - Ensure you have access to Claude models in your region
365
+ - Check IAM permissions for Bedrock
366
+
367
+ ### AI Tutor Not Working
368
+ - Check `NEBIUS_API_KEY` is set
369
+ - Ensure `NEBIUS_EMBEDDING_MODEL` is set to `Qwen3-Embedding-8B` for vector search to work
370
+ - Verify `chromadb` is installed: `pip install chromadb`
371
+
372
+ ### Gradio Interface Issues
373
+ - Ensure port 7861 is not blocked
374
+ - Try clearing browser cache
375
+ - Check console logs for errors
376
+
377
+ ---
378
+
379
+ ## 🤝 Contributing
380
+
381
+ Contributions are welcome! Please follow the existing code structure:
382
+
383
+ - Keep tools modular in `vapt_tools.py`
384
+ - Add configuration in `config.py`
385
+ - Update `.env.template` for new variables
386
+ - Follow Python best practices (PEP 8)
387
+ - Add docstrings for new functions
388
+
389
+ ---
390
+
391
+ ## 📜 License
392
+
393
+ MIT License - See LICENSE file for details
394
+
395
+ ---
396
+
397
+ ## ⚠️ Disclaimer
398
+
399
+ This tool is for **authorized security testing only**. Always obtain proper authorization before testing any API endpoints. Unauthorized testing may be illegal and unethical.
400
+
401
+ ---
402
+
403
+ ## 🙏 Acknowledgments
404
+
405
+ Built for **MCP's 1st Birthday Hackathon** hosted by **Anthropic** and **Gradio**.
406
+
407
+ **Technologies Used**:
408
+ - [Anthropic Claude Agent SDK](https://github.com/anthropics/anthropic-sdk-python)
409
+ - [Gradio](https://gradio.app/)
410
+ - [Postman MCP Server](https://mcp.postman.com/)
411
+ - [Chroma](https://www.trychroma.com/)
412
+ - [Nebius Token Factory](https://nebius.com/)
413
+
414
+ ---
__init__.py ADDED
File without changes
ai_tutor.py ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AI Security Tutor for VAPT Agent.
3
+
4
+ Now powered by Nebius Token Factory (OpenAI-compatible API) with Chroma vector search.
5
+
6
+ Key behaviours:
7
+ - Uses a Nebius-hosted model for chat.
8
+ - Uses a Nebius embedding model + Chroma to build a vector index over the
9
+ VAPT Markdown report.
10
+ - The index is built ONCE per report (per process) and automatically rebuilt
11
+ if the report content changes.
12
+ - Ensures that vectors from one report are never reused for another report
13
+ by hashing the report content and recreating the index when it changes.
14
+ - Optionally enriches answers with web search via Tavily.
15
+
16
+ Environment variables:
17
+
18
+ # Nebius (required)
19
+ - NEBIUS_API_KEY : Nebius Token Factory API key
20
+ - NEBIUS_BASE_URL : (optional) e.g. "https://api.tokenfactory.nebius.com/v1"
21
+ - NEBIUS_TUTOR_MODEL : (optional) chat model id for tutor
22
+ - NEBIUS_EMBEDDING_MODEL : embedding model id for vector search (REQUIRED to enable Chroma search)
23
+
24
+ # Optional web search
25
+ - TAVILY_API_KEY : enables web search if set
26
+ """
27
+
28
+ import os
29
+ import hashlib
30
+ from typing import List, Tuple, Dict
31
+ from dataclasses import dataclass
32
+
33
+ import requests
34
+ from openai import OpenAI
35
+
36
+ from prompt import get_tutor_system_prompt
37
+
38
+ # Try to import Chroma, but degrade gracefully if not installed
39
+ try:
40
+ import chromadb
41
+
42
+ CHROMA_AVAILABLE = True
43
+ except ImportError:
44
+ chromadb = None
45
+ CHROMA_AVAILABLE = False
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Simple helpers
50
+ # ---------------------------------------------------------------------------
51
+
52
+
53
+ def _normalize(text: str) -> str:
54
+ return text.lower()
55
+
56
+
57
+ def _extract_report_sections(
58
+ report_md: str, max_section_chars: int = 2000
59
+ ) -> List[str]:
60
+ """
61
+ Split the markdown report into logical sections based on '## ' headings.
62
+
63
+ If sections are very large, they are further split into smaller chunks.
64
+ """
65
+ if not report_md:
66
+ return []
67
+
68
+ sections: List[str] = []
69
+ current: List[str] = []
70
+
71
+ lines = report_md.splitlines()
72
+ for line in lines:
73
+ if line.startswith("## "):
74
+ if current:
75
+ sections.append("\n".join(current).strip())
76
+ current = []
77
+ current.append(line)
78
+
79
+ if current:
80
+ sections.append("\n".join(current).strip())
81
+
82
+ if not sections:
83
+ sections = [report_md]
84
+
85
+ # Further split oversized sections into smaller chunks
86
+ final_chunks: List[str] = []
87
+ for sec in sections:
88
+ if len(sec) <= max_section_chars:
89
+ final_chunks.append(sec)
90
+ else:
91
+ # naive split by paragraphs
92
+ paras = sec.split("\n\n")
93
+ chunk: List[str] = []
94
+ size = 0
95
+ for p in paras:
96
+ p_len = len(p) + 2
97
+ if size + p_len > max_section_chars and chunk:
98
+ final_chunks.append("\n\n".join(chunk))
99
+ chunk = [p]
100
+ size = p_len
101
+ else:
102
+ chunk.append(p)
103
+ size += p_len
104
+ if chunk:
105
+ final_chunks.append("\n\n".join(chunk))
106
+
107
+ return final_chunks
108
+
109
+
110
+ def _web_search_tavily(query: str, max_results: int = 3) -> str:
111
+ """
112
+ Optional: perform web search via Tavily Search API.
113
+
114
+ Requires TAVILY_API_KEY in env. If not present or call fails,
115
+ returns an empty string and the tutor will just rely on the report.
116
+ """
117
+ api_key = os.getenv("TAVILY_API_KEY")
118
+ if not api_key:
119
+ return ""
120
+
121
+ try:
122
+ payload = {
123
+ "api_key": api_key,
124
+ "query": query,
125
+ "max_results": max_results,
126
+ "include_answer": True,
127
+ "search_depth": "basic",
128
+ }
129
+ resp = requests.post(
130
+ "https://api.tavily.com/search",
131
+ json=payload,
132
+ timeout=15,
133
+ )
134
+ resp.raise_for_status()
135
+ data = resp.json()
136
+
137
+ parts: List[str] = []
138
+ answer = data.get("answer")
139
+ if answer:
140
+ parts.append(f"Direct answer: {answer}")
141
+
142
+ results = data.get("results") or []
143
+ for r in results[:max_results]:
144
+ title = r.get("title") or "Untitled"
145
+ url = r.get("url") or ""
146
+ content = r.get("content") or ""
147
+ parts.append(f"- {title}\n {content[:300]}...\n Source: {url}")
148
+
149
+ return "\n".join(parts) if parts else ""
150
+ except Exception:
151
+ # Fail silently – we don't want the tutor to break if search fails
152
+ return ""
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # Security Tutor with Nebius + Chroma
157
+ # ---------------------------------------------------------------------------
158
+
159
+
160
+ @dataclass
161
+ class TutorConfig:
162
+ base_url: str
163
+ api_key: str
164
+ model: str
165
+ embedding_model: str | None
166
+
167
+
168
+ class SecurityTutor:
169
+ """AI-powered security education assistant backed by Nebius + Chroma."""
170
+
171
+ def __init__(self):
172
+ """
173
+ Initialize the Security Tutor with Nebius OpenAI-compatible client.
174
+
175
+ Required:
176
+ - NEBIUS_API_KEY
177
+
178
+ Optional:
179
+ - NEBIUS_BASE_URL (defaults to Nebius Token Factory base URL)
180
+ - NEBIUS_TUTOR_MODEL
181
+ - NEBIUS_EMBEDDING_MODEL (required to enable Chroma vector search)
182
+ """
183
+ api_key = os.getenv("NEBIUS_API_KEY")
184
+
185
+ if not api_key:
186
+ self.client = None
187
+ self.available = False
188
+ self.config = None
189
+ self.chroma_client = None
190
+ self.vector_enabled = False
191
+ self._raw_report = ""
192
+ self._report_hash = None
193
+ self._collection = None
194
+ return
195
+
196
+ base_url = os.getenv(
197
+ "NEBIUS_BASE_URL",
198
+ "https://api.tokenfactory.nebius.com/v1", # Nebius OpenAI-compatible base URL
199
+ )
200
+ model = os.getenv(
201
+ "NEBIUS_TUTOR_MODEL",
202
+ "meta-llama/Meta-Llama-3.1-70B-Instruct", # example; override with your chosen model
203
+ )
204
+ embedding_model = os.getenv(
205
+ "NEBIUS_EMBEDDING_MODEL", # REQUIRED for vector search
206
+ None,
207
+ )
208
+
209
+ self.config = TutorConfig(
210
+ base_url=base_url,
211
+ api_key=api_key,
212
+ model=model,
213
+ embedding_model=embedding_model,
214
+ )
215
+ self.client = OpenAI(base_url=self.config.base_url, api_key=self.config.api_key)
216
+ self.available = True
217
+
218
+ # Chroma setup
219
+ if CHROMA_AVAILABLE and self.config.embedding_model:
220
+ # Ephemeral in-memory store is fine for a single process
221
+ self.chroma_client = chromadb.EphemeralClient()
222
+ self.vector_enabled = True
223
+ else:
224
+ self.chroma_client = None
225
+ self.vector_enabled = False
226
+
227
+ # Per-report state
228
+ self._raw_report: str = ""
229
+ self._report_hash: str | None = None
230
+ self._collection = None # Chroma collection holding current report vectors
231
+
232
+ # ------------------------------------------------------------------ #
233
+ # Public entry point
234
+ # ------------------------------------------------------------------ #
235
+
236
+ def chat(
237
+ self,
238
+ message: str,
239
+ report_context: str,
240
+ history: List[Tuple[str, str]],
241
+ ) -> str:
242
+ """
243
+ Handle a chat message from the user.
244
+
245
+ Args:
246
+ message: User's question
247
+ report_context: Full VAPT report markdown for THIS user/run
248
+ history: Previous chat messages [(user_msg, assistant_msg), ...]
249
+
250
+ Behaviour:
251
+ - If the report content has changed since last call, rebuild the
252
+ vector index just once and store it.
253
+ - Then run vector search on the stored index for this question.
254
+ - Never reuse vectors from a previous report for the new report.
255
+ """
256
+ if not self.available or not self.client:
257
+ return (
258
+ "🔧 AI Tutor is not configured yet.\n\n"
259
+ "Please set NEBIUS_API_KEY (and optionally NEBIUS_TUTOR_MODEL) "
260
+ "in your environment to enable the tutor."
261
+ )
262
+
263
+ # 1) Ensure the index is up-to-date for THIS report
264
+ self._ensure_report_index(report_context)
265
+
266
+ # 2) Retrieve relevant snippets from the currently indexed report
267
+ report_snippets = self._search_report_with_vectors(message)
268
+
269
+ # 3) Optional web search (if Tavily API key is configured)
270
+ web_snippets = _web_search_tavily(message)
271
+ web_note = (
272
+ "\n\n---\n\nWeb search snippets:\n" + web_snippets
273
+ if web_snippets
274
+ else "\n\n(Web search not configured or returned no results.)"
275
+ )
276
+
277
+ # 4) Build system prompt with clear grounding instructions
278
+ system_prompt = self._build_system_prompt(
279
+ report_snippets=report_snippets,
280
+ include_web=bool(web_snippets),
281
+ )
282
+
283
+ # 5) Build messages (system + history + current question)
284
+ messages: List[Dict[str, str]] = [{"role": "system", "content": system_prompt}]
285
+
286
+ for user_msg, assistant_msg in history:
287
+ if user_msg:
288
+ messages.append({"role": "user", "content": user_msg})
289
+ if assistant_msg:
290
+ messages.append({"role": "assistant", "content": assistant_msg})
291
+
292
+ user_content = (
293
+ f"{message}\n\n"
294
+ "-----\n\n"
295
+ "Use the following VAPT report excerpts as your primary source of truth:\n\n"
296
+ f"{report_snippets}\n"
297
+ f"{web_note}"
298
+ )
299
+ messages.append({"role": "user", "content": user_content})
300
+
301
+ try:
302
+ completion = self.client.chat.completions.create(
303
+ model=self.config.model,
304
+ messages=messages,
305
+ temperature=0.4,
306
+ max_tokens=800,
307
+ )
308
+ return completion.choices[0].message.content
309
+ except Exception as e:
310
+ return f"❌ Error communicating with Security Tutor (Nebius): {str(e)}"
311
+
312
+ # ------------------------------------------------------------------ #
313
+ # Report index management (build once per report)
314
+ # ------------------------------------------------------------------ #
315
+
316
+ def _ensure_report_index(self, report_md: str) -> None:
317
+ """
318
+ Ensure that the vector index reflects the given report.
319
+
320
+ - If no report or same report as before -> do nothing.
321
+ - If a different report -> rebuild vectors so we NEVER reuse old vectors
322
+ for a new report.
323
+ """
324
+ report_md = report_md or ""
325
+
326
+ # If we never had a report and still don't, nothing to do
327
+ if not report_md and not self._raw_report:
328
+ return
329
+
330
+ new_hash = hashlib.sha256(report_md.encode("utf-8")).hexdigest()
331
+
332
+ # If hash matches, it's the same report -> keep existing vectors
333
+ if self._report_hash == new_hash:
334
+ return
335
+
336
+ # Report changed: update internal state and rebuild index
337
+ self._raw_report = report_md
338
+ self._report_hash = new_hash
339
+
340
+ if (
341
+ not self.vector_enabled
342
+ or not self.chroma_client
343
+ or not self.config.embedding_model
344
+ ):
345
+ # We still store the report, so fallback search can use it
346
+ self._collection = None
347
+ return
348
+
349
+ # Build a fresh collection just for this report
350
+ sections = _extract_report_sections(self._raw_report)
351
+ if not sections:
352
+ self._collection = None
353
+ return
354
+
355
+ # Collection name derived from hash to avoid mixing
356
+ coll_name = f"vapt_report_{self._report_hash[:8]}"
357
+
358
+ # Create or get collection; then clear any previous contents
359
+ self._collection = self.chroma_client.get_or_create_collection(name=coll_name)
360
+ try:
361
+ self._collection.delete(where={})
362
+ except Exception:
363
+ # Older Chroma versions might not like empty filters; safely ignore
364
+ pass
365
+
366
+ # Embed and add sections
367
+ ids = [f"chunk-{i}" for i in range(len(sections))]
368
+ embeddings = self._embed_texts(sections)
369
+
370
+ self._collection.add(ids=ids, documents=sections, embeddings=embeddings)
371
+
372
+ # ------------------------------------------------------------------ #
373
+ # Vector search over report using Chroma + Nebius embeddings
374
+ # ------------------------------------------------------------------ #
375
+
376
+ def _embed_texts(self, texts: List[str]) -> List[List[float]]:
377
+ """
378
+ Create embeddings for a list of texts using Nebius embedding model.
379
+ """
380
+ if not self.config.embedding_model:
381
+ raise RuntimeError(
382
+ "NEBIUS_EMBEDDING_MODEL is not set; cannot perform vector search."
383
+ )
384
+
385
+ resp = self.client.embeddings.create(
386
+ model=self.config.embedding_model,
387
+ input=texts,
388
+ )
389
+ return [item.embedding for item in resp.data]
390
+
391
+ def _search_report_with_vectors(self, question: str, top_k: int = 4) -> str:
392
+ """
393
+ Use the current Chroma collection (for the current report) to retrieve
394
+ the most relevant chunks. Falls back to simple truncation of the report
395
+ if vector search is not available.
396
+ """
397
+ if not self._raw_report:
398
+ return "No VAPT report is currently available."
399
+
400
+ # If vector search is not enabled or we have no collection, fallback
401
+ if not self.vector_enabled or not self._collection:
402
+ # Cheap fallback: Executive Summary + Key Findings, or first 2000 chars
403
+ sections = _extract_report_sections(self._raw_report)
404
+ fallback = [
405
+ s
406
+ for s in sections
407
+ if "executive summary" in _normalize(s)
408
+ or "key findings" in _normalize(s)
409
+ ]
410
+ if fallback:
411
+ return "\n\n---\n\n".join(fallback)[:2000]
412
+ return self._raw_report[:2000]
413
+
414
+ try:
415
+ # Embed the question and query the collection
416
+ q_embedding = self._embed_texts([question])[0]
417
+
418
+ results = self._collection.query(
419
+ query_embeddings=[q_embedding],
420
+ n_results=top_k,
421
+ )
422
+
423
+ docs = results.get("documents", [[]])[0] if results else []
424
+ if not docs:
425
+ return self._raw_report[:2000]
426
+
427
+ joined = "\n\n---\n\n".join(docs)
428
+ return joined[:2000]
429
+ except Exception:
430
+ # Any failure -> fall back to raw report
431
+ return self._raw_report[:2000]
432
+
433
+ # ------------------------------------------------------------------ #
434
+ # System prompt builder
435
+ # ------------------------------------------------------------------ #
436
+
437
+ def _build_system_prompt(self, report_snippets: str, include_web: bool) -> str:
438
+ """
439
+ Build the system prompt for the AI tutor.
440
+
441
+ Args:
442
+ report_snippets: Text retrieved from the VAPT report
443
+ include_web: Whether web search snippets are available
444
+
445
+ Returns:
446
+ System prompt string
447
+ """
448
+ return get_tutor_system_prompt(report_snippets, include_web)
449
+
450
+
451
+ # Global tutor instance (shared within the process)
452
+ _tutor_instance: SecurityTutor | None = None
453
+
454
+
455
+ def get_tutor() -> SecurityTutor:
456
+ """
457
+ Get or create the global SecurityTutor instance.
458
+
459
+ Note: Vectors are tied to the report markdown passed into `chat()`.
460
+ Whenever a new report is used, the tutor automatically rebuilds its
461
+ index so that vectors from a previous report are never reused.
462
+ """
463
+ global _tutor_instance
464
+ if _tutor_instance is None:
465
+ _tutor_instance = SecurityTutor()
466
+ return _tutor_instance
app.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio Web Interface for VAPT Agent
2
+
3
+ """
4
+ Modern Gradio UI for the VAPT (Vulnerability Assessment and Penetration Testing) agent.
5
+
6
+ Features:
7
+ - Input API endpoint, HTTP method, and optional API key
8
+ - Real-time progress streaming
9
+ - Downloadable Markdown report
10
+ - Visual Dashboard (risk gauge & severity pie chart)
11
+ - AI Security Tutor (interactive Q&A about the report)
12
+ """
13
+
14
+ import asyncio
15
+ import gradio as gr
16
+ from datetime import datetime
17
+ import threading
18
+ import time
19
+ from typing import Optional, Generator, List, Tuple
20
+
21
+ from vapt_agent import run_vapt_agent_with_callback
22
+ from config import VAPTConfig
23
+ from dashboard_utils import (
24
+ parse_vapt_report,
25
+ calculate_risk_score,
26
+ create_severity_chart,
27
+ create_risk_gauge,
28
+ )
29
+ from ai_tutor import get_tutor
30
+
31
+ import os
32
+
33
+ CSS_PATH = os.path.join(os.path.dirname(__file__), "vapt_styles.css")
34
+
35
+
36
+ def load_custom_css(path: str = CSS_PATH) -> str:
37
+ try:
38
+ with open(path, "r", encoding="utf-8") as f:
39
+ return f.read()
40
+ except FileNotFoundError:
41
+ # Fail silently; app will still run without custom CSS
42
+ return ""
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Helper: run the VAPT agent and stream updates to Gradio
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ def run_security_test(
51
+ api_endpoint: str,
52
+ http_method: str,
53
+ api_key: Optional[str] = None,
54
+ ) -> Generator[Tuple[str, str, str], None, None]:
55
+ """Yield progress, report markdown and report file path for Gradio.
56
+
57
+ The function validates inputs, starts the VAPT agent in a background thread,
58
+ and periodically yields any new progress messages.
59
+ """
60
+ # ---------- Validation ----------
61
+ if not api_endpoint or not api_endpoint.strip():
62
+ yield (
63
+ "❌ Error: API endpoint is required",
64
+ "## Error\n\nPlease provide a valid API endpoint URL.",
65
+ None,
66
+ )
67
+ return
68
+ if not api_endpoint.startswith(("http://", "https://")):
69
+ yield (
70
+ "❌ Error: Invalid URL format",
71
+ "## Error\n\nAPI endpoint must start with `http://` or `https://`.",
72
+ None,
73
+ )
74
+ return
75
+
76
+ # ---------- Progress handling ----------
77
+ progress_messages: List[str] = []
78
+ lock = threading.Lock()
79
+
80
+ def add_progress(msg: str) -> str:
81
+ with lock:
82
+ progress_messages.append(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}")
83
+ return "\n".join(progress_messages)
84
+
85
+ def progress_callback(msg: str):
86
+ with lock:
87
+ progress_messages.append(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}")
88
+
89
+ # Initial message
90
+ yield (
91
+ add_progress("🚀 Initializing VAPT Agent..."),
92
+ "## Starting Security Test\n\nPlease wait while we assess your API endpoint...",
93
+ None,
94
+ )
95
+
96
+ # Prepare request headers
97
+ headers = {"Content-Type": "application/json", "User-Agent": "VAPT-Agent/1.0"}
98
+ if api_key and api_key.strip():
99
+ headers["Authorization"] = f"Bearer {api_key.strip()}"
100
+ yield (
101
+ add_progress("🔑 API key provided – will test authenticated endpoints"),
102
+ "## Starting Security Test\n\nPreparing to test with authentication...",
103
+ None,
104
+ )
105
+
106
+ # ---------- Run agent in background thread ----------
107
+ result = {
108
+ "report_content": None,
109
+ "report_file_path": None,
110
+ "error": None,
111
+ "done": False,
112
+ }
113
+
114
+ def agent_worker():
115
+ try:
116
+ loop = asyncio.new_event_loop()
117
+ asyncio.set_event_loop(loop)
118
+ content, path = loop.run_until_complete(
119
+ run_vapt_agent_with_callback(
120
+ api_endpoint=api_endpoint,
121
+ method=http_method,
122
+ headers=headers,
123
+ progress_callback=progress_callback,
124
+ )
125
+ )
126
+ loop.close()
127
+ result["report_content"] = content
128
+ result["report_file_path"] = path
129
+ except asyncio.TimeoutError:
130
+ result["error"] = "Timeout: Security test took too long"
131
+ except Exception as e:
132
+ result["error"] = str(e)
133
+ finally:
134
+ result["done"] = True
135
+
136
+ # Connect to LLM backend – just a placeholder progress update
137
+ yield (
138
+ add_progress("🔌 Connecting to security engine..."),
139
+ "## Starting Security Test\n\nConnecting...",
140
+ None,
141
+ )
142
+ threading.Thread(target=agent_worker, daemon=True).start()
143
+
144
+ # ---------- Poll for updates ----------
145
+ last_len = 0
146
+ while not result["done"]:
147
+ time.sleep(0.5)
148
+ with lock:
149
+ if len(progress_messages) > last_len:
150
+ yield (
151
+ "\n".join(progress_messages),
152
+ "## Security Test in Progress\n\nPlease wait while the agent performs testing...",
153
+ None,
154
+ )
155
+ last_len = len(progress_messages)
156
+
157
+ # ---------- Final handling ----------
158
+ if result["error"]:
159
+ err = result["error"]
160
+ if "Timeout" in err:
161
+ yield (
162
+ add_progress(f"⏱️ {err}"),
163
+ "## Error\n\n**Timeout Error**\n\nThe assessment exceeded the allowed time.",
164
+ None,
165
+ )
166
+ else:
167
+ yield (
168
+ add_progress(f"❌ Error: {err}"),
169
+ f"## Error\n\n**Exception Occurred**\n\n```\n{err}\n```\n\nPlease check configuration and retry.",
170
+ None,
171
+ )
172
+ else:
173
+ # Success – return report and file path
174
+ yield (
175
+ add_progress("✅ Security assessment completed successfully!"),
176
+ result["report_content"] or "## Error\n\nNo report was generated.",
177
+ result["report_file_path"],
178
+ )
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # Gradio UI construction
183
+ # ---------------------------------------------------------------------------
184
+
185
+
186
+ def create_gradio_interface() -> gr.Blocks:
187
+ with gr.Blocks(title="VAPT Agent") as iface:
188
+
189
+ # Inject custom CSS – light theme, white cards, improved readability
190
+ custom_css = load_custom_css()
191
+ if custom_css:
192
+ gr.HTML(f"<style>{custom_css}</style>")
193
+
194
+ # --- Header ---
195
+ with gr.Row(elem_id="app-header"):
196
+ with gr.Column(scale=6):
197
+ gr.Markdown(
198
+ """
199
+ <div>
200
+ <div class="badge-pill">
201
+ <span class="dot"></span>
202
+ <span>Agentic VAPT • API Security</span>
203
+ </div>
204
+ <h1>VAPT Agent Dashboard</h1>
205
+ <p>
206
+ Generate API specs, run automated vulnerability tests, and explore results with an AI security tutor. Gain clear insights into API risks, misconfigurations, and recommended remediation steps in dashboard.
207
+ </p>
208
+ </div>
209
+ """,
210
+ elem_id="header-title",
211
+ )
212
+ with gr.Column(scale=4):
213
+ gr.Markdown(
214
+ """
215
+ **Workflow Overview**
216
+
217
+ 1. Provide an API endpoint and method
218
+ 2. Agent discovers endpoints and builds the API spec using Postman MCP
219
+ 3. Customized VAPT MCP tools run automated security tests
220
+ 4. Dashboard + Tutor help you interpret and fix issues
221
+ """,
222
+ )
223
+
224
+ # --- Main two-column layout ---
225
+ with gr.Row():
226
+ # Left: API configuration
227
+ with gr.Column(scale=5):
228
+ with gr.Group(elem_id="config-card", elem_classes=["section-card"]):
229
+ gr.Markdown("### 📋 API Configuration")
230
+
231
+ api_endpoint = gr.Textbox(
232
+ label="API Endpoint URL",
233
+ placeholder="https://sandbox.api.sap.com/SAPCALM/calm-tasks/v1/tasks?projectId=111",
234
+ value="https://sandbox.api.sap.com/SAPCALM/calm-tasks/v1/tasks?projectId=111",
235
+ info="Full URL of the API endpoint to test",
236
+ )
237
+ http_method = gr.Dropdown(
238
+ label="HTTP Method",
239
+ choices=["GET", "POST", "PUT", "DELETE", "PATCH"],
240
+ value="GET",
241
+ info="Select the HTTP method for the endpoint",
242
+ )
243
+ api_key = gr.Textbox(
244
+ label="API Key (Optional)",
245
+ placeholder="Enter your API key or Bearer token",
246
+ type="password",
247
+ value="Ww9aGPGeGoDGCFetcBtsaEtGOpGSUNXp",
248
+ info="If the API requires authentication, provide the key here",
249
+ )
250
+ with gr.Row():
251
+ submit_btn = gr.Button(
252
+ "🚀 Start Security Test", variant="primary", size="lg"
253
+ )
254
+ clear_btn = gr.Button(
255
+ "🔄 Reset", variant="secondary", elem_id="reset-btn"
256
+ )
257
+
258
+ gr.HTML(
259
+ """
260
+ <div id="disclaimer-box">
261
+ ⚠️ <strong>Authorized use only:</strong> Run this tool only against systems and APIs you are explicitly allowed to test.
262
+ </div>
263
+ """
264
+ )
265
+
266
+ # Right: Results area
267
+ with gr.Column(scale=7):
268
+ with gr.Group(elem_id="results-card", elem_classes=["section-card"]):
269
+ gr.Markdown("### 📊 Security Assessment Results")
270
+
271
+ with gr.Tab("Live Progress"):
272
+ progress_output = gr.Textbox(
273
+ label="Agent Activity Log",
274
+ lines=15,
275
+ max_lines=20,
276
+ interactive=False,
277
+ show_label=False,
278
+ placeholder="Agent activity will appear here...",
279
+ )
280
+
281
+ with gr.Tab("Security Report"):
282
+ report_file = gr.File(
283
+ label="📥 Download Report (.md)",
284
+ interactive=False,
285
+ visible=True,
286
+ )
287
+ report_output = gr.Markdown(
288
+ value="Security report will appear here after the test completes...",
289
+ label="VAPT Report",
290
+ elem_id="security-report-md",
291
+ )
292
+
293
+ with gr.Tab("Dashboard"):
294
+ # Row that we'll center via CSS
295
+ with gr.Row(elem_id="dashboard-row"):
296
+ with gr.Column(scale=1, elem_id="risk-col"):
297
+ risk_gauge = gr.Plot(label="Risk Score")
298
+ with gr.Column(scale=1, elem_id="severity-col"):
299
+ severity_pie = gr.Plot(
300
+ label="Vulnerability Distribution"
301
+ )
302
+
303
+ top_findings = gr.Markdown(
304
+ "Run a security test to see summarized key findings..."
305
+ )
306
+
307
+ with gr.Tab("Security Tutor"):
308
+ with gr.Column(elem_id="tutor-section"):
309
+ gr.Markdown(
310
+ """
311
+ <div style="font-size: 0.95rem; line-height: 1.5; margin-bottom: 0.4rem;">
312
+ <strong>Ask questions about your security report.</strong><br/>
313
+ Get clear explanations, remediation guidance, and best-practice advice.
314
+ </div>
315
+ """
316
+ )
317
+ chatbot = gr.Chatbot(
318
+ label="Security Tutor",
319
+ height=380,
320
+ elem_id="tutor-chat",
321
+ )
322
+
323
+ with gr.Row(elem_id="tutor-input-row"):
324
+ tutor_input = gr.Textbox(
325
+ label="Your Question",
326
+ placeholder="e.g., What is SQL injection and how do I fix it?",
327
+ lines=2,
328
+ scale=4,
329
+ show_label=True,
330
+ )
331
+ tutor_btn = gr.Button("Ask", variant="primary", scale=1)
332
+
333
+ gr.HTML(
334
+ """
335
+ <div id="tutor-examples">
336
+ <strong>Example questions you can ask:</strong>
337
+ <ul>
338
+ <li>What is the most critical issue in my report?</li>
339
+ <li>How do I fix CORS policy issues?</li>
340
+ <li>Explain SQL injection in simple terms.</li>
341
+ <li>What should I fix first to reduce risk quickly?</li>
342
+ </ul>
343
+ </div>
344
+ """
345
+ )
346
+
347
+ # -------------------------------------------------------------------
348
+ # Event bindings
349
+ # -------------------------------------------------------------------
350
+
351
+ # VAPT run
352
+ submit_btn.click(
353
+ fn=run_security_test,
354
+ inputs=[api_endpoint, http_method, api_key],
355
+ outputs=[progress_output, report_output, report_file],
356
+ show_progress=True,
357
+ )
358
+
359
+ # Reset
360
+ clear_btn.click(
361
+ fn=lambda: (
362
+ "https://sandbox.api.sap.com/SAPCALM/calm-tasks/v1/tasks?projectId=111",
363
+ "GET",
364
+ "",
365
+ "",
366
+ "Security report will appear here after the test completes...",
367
+ None,
368
+ ),
369
+ inputs=[],
370
+ outputs=[
371
+ api_endpoint,
372
+ http_method,
373
+ api_key,
374
+ progress_output,
375
+ report_output,
376
+ report_file,
377
+ ],
378
+ )
379
+
380
+ # Dashboard updates – triggered after a successful report
381
+ def update_dashboard(report_md: str):
382
+ data = parse_vapt_report(report_md)
383
+ sev = data["severities"]
384
+ risk = calculate_risk_score(sev)
385
+ return (
386
+ create_risk_gauge(risk),
387
+ create_severity_chart(sev),
388
+ (
389
+ "\n".join(data.get("findings", [])[:5])
390
+ if data.get("findings")
391
+ else "No findings detected."
392
+ ),
393
+ )
394
+
395
+ report_output.change(
396
+ fn=update_dashboard,
397
+ inputs=[report_output],
398
+ outputs=[risk_gauge, severity_pie, top_findings],
399
+ )
400
+
401
+ # AI Tutor interaction
402
+ # Gradio Chatbot (v6) uses "messages" format: list of {"role": ..., "content": ...}
403
+ # We convert that to (user, assistant) pairs for the tutor, then convert back.
404
+ def tutor_respond(question, history, report_md: str):
405
+ # 1) Convert Gradio "messages" history -> list of (user, assistant) pairs
406
+ pairs: List[Tuple[str, str]] = []
407
+ current_user = None
408
+
409
+ for msg in history or []:
410
+ if isinstance(msg, dict):
411
+ role = msg.get("role")
412
+ content = msg.get("content", "")
413
+ elif isinstance(msg, (list, tuple)) and len(msg) == 2:
414
+ # Backward compatibility: skip old tuple-style entries
415
+ continue
416
+ else:
417
+ continue
418
+
419
+ if role == "user":
420
+ current_user = content
421
+ elif role == "assistant":
422
+ if current_user is None:
423
+ current_user = ""
424
+ pairs.append((current_user, content))
425
+ current_user = None
426
+
427
+ # 2) Call the tutor with pairs
428
+ tutor = get_tutor()
429
+ answer = tutor.chat(question, report_md, pairs)
430
+
431
+ # 3) Append new user + assistant messages in Gradio's messages format
432
+ new_history = list(history or [])
433
+ new_history.append({"role": "user", "content": question})
434
+ new_history.append({"role": "assistant", "content": answer})
435
+
436
+ # Clear the input textbox
437
+ return new_history, ""
438
+
439
+ tutor_btn.click(
440
+ fn=tutor_respond,
441
+ inputs=[tutor_input, chatbot, report_output],
442
+ outputs=[chatbot, tutor_input],
443
+ )
444
+
445
+ return iface
446
+
447
+
448
+ # ---------------------------------------------------------------------------
449
+ # Main entry point
450
+ # ---------------------------------------------------------------------------
451
+
452
+
453
+ def main():
454
+ print("=" * 80)
455
+ print("VAPT Agent - Gradio Web Interface")
456
+ print("=" * 80)
457
+ try:
458
+ cfg = VAPTConfig()
459
+ print(f"✓ Configuration loaded successfully")
460
+ print(f" Provider: {'AWS Bedrock' if cfg.use_bedrock else 'Anthropic API'}")
461
+ if cfg.use_bedrock:
462
+ print(f" Region: {cfg.aws_region}")
463
+ print()
464
+ except Exception as exc:
465
+ print(f"❌ Configuration error: {exc}")
466
+ print("Please check your .env file and ensure all required variables are set.")
467
+ return
468
+
469
+ iface = create_gradio_interface()
470
+ print("Starting Gradio server...")
471
+ print("=" * 80)
472
+ iface.launch(server_name="0.0.0.0", server_port=7861, share=True, inbrowser=True)
473
+
474
+
475
+ if __name__ == "__main__":
476
+ main()
config.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for VAPT Agent.
3
+
4
+ This module handles all configuration loading from environment variables
5
+ and provides a centralized config object.
6
+ """
7
+
8
+ import os
9
+ from typing import Optional
10
+ from dotenv import load_dotenv
11
+
12
+ load_dotenv(override=True)
13
+
14
+
15
+ class VAPTConfig:
16
+ """Configuration class for VAPT Agent."""
17
+
18
+ def __init__(self):
19
+ """Initialize configuration from environment variables."""
20
+
21
+ # ====================================================================
22
+ # AWS Configuration
23
+ # ====================================================================
24
+ self.use_bedrock = os.getenv("CLAUDE_CODE_USE_BEDROCK", "0") == "1"
25
+ self.aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "")
26
+ self.aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "")
27
+ self.aws_region = os.getenv("AWS_REGION", "us-east-1")
28
+
29
+ # ====================================================================
30
+ # Model Configuration
31
+ # ====================================================================
32
+ if self.use_bedrock:
33
+ # Default to Bedrock model identifier
34
+ default_model = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
35
+ else:
36
+ # Default to Anthropic API model
37
+ default_model = "claude-sonnet-4-20250514"
38
+
39
+ self.model_name = os.getenv("ANTHROPIC_MODEL", default_model)
40
+
41
+ # ====================================================================
42
+ # Postman Configuration
43
+ # ====================================================================
44
+ self.postman_api_key = os.getenv("POSTMAN_API_KEY", "")
45
+
46
+ # ====================================================================
47
+ # Test API Configuration
48
+ # ====================================================================
49
+ self.test_api_endpoint = os.getenv(
50
+ "TEST_API_ENDPOINT", "https://sandbox.api.sap.com/SAPCALM/calm-tasks/v1/tasks?projectId=111"
51
+ )
52
+ self.test_api_method = os.getenv("TEST_API_METHOD", "GET")
53
+ self.test_api_key = os.getenv("TEST_API_KEY", "")
54
+
55
+ # ====================================================================
56
+ # Agent Configuration
57
+ # ====================================================================
58
+ self.max_turns = int(os.getenv("MAX_TURNS", "100"))
59
+ self.timeout_seconds = int(os.getenv("TIMEOUT_SECONDS", "600"))
60
+ self.max_retries = int(os.getenv("MAX_RETRIES", "3"))
61
+
62
+ # Validate required configuration
63
+ self._validate()
64
+
65
+ def _validate(self):
66
+ """Validate required configuration values."""
67
+ errors = []
68
+
69
+ if not self.postman_api_key:
70
+ errors.append("POSTMAN_API_KEY is required")
71
+
72
+ if self.use_bedrock:
73
+ if not self.aws_access_key_id:
74
+ errors.append("AWS_ACCESS_KEY_ID is required when using Bedrock")
75
+ if not self.aws_secret_access_key:
76
+ errors.append("AWS_SECRET_ACCESS_KEY is required when using Bedrock")
77
+
78
+ if errors:
79
+ raise ValueError(
80
+ "Configuration validation failed:\n"
81
+ + "\n".join(f" - {e}" for e in errors)
82
+ )
83
+
84
+ def to_dict(self):
85
+ """Return configuration as dictionary (excluding sensitive data)."""
86
+ return {
87
+ "use_bedrock": self.use_bedrock,
88
+ "aws_region": self.aws_region,
89
+ "model_name": self.model_name,
90
+ "test_api_endpoint": self.test_api_endpoint,
91
+ "test_api_method": self.test_api_method,
92
+ "max_turns": self.max_turns,
93
+ "timeout_seconds": self.timeout_seconds,
94
+ "max_retries": self.max_retries,
95
+ }
96
+
97
+ def __repr__(self):
98
+ """String representation of configuration."""
99
+ return f"VAPTConfig({self.to_dict()})"
dashboard_utils.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Dashboard utilities for VAPT Agent.
3
+
4
+ Provides functions to parse VAPT reports, calculate risk scores,
5
+ and generate visualizations for the dashboard.
6
+ """
7
+
8
+ import re
9
+ from typing import Dict, List, Tuple
10
+ import plotly.graph_objects as go
11
+
12
+
13
+ def parse_vapt_report(report_md: str) -> Dict:
14
+ """Parse VAPT report markdown to extract vulnerability data.
15
+
16
+ Args:
17
+ report_md: Markdown content of VAPT report
18
+
19
+ Returns:
20
+ Dictionary with vulnerability counts by severity and list of findings
21
+ """
22
+ if not report_md or "Error" in report_md[:100]:
23
+ return {
24
+ "severities": {
25
+ "critical": 0,
26
+ "high": 0,
27
+ "medium": 0,
28
+ "low": 0,
29
+ "info": 0,
30
+ },
31
+ "total": 0,
32
+ "findings": [],
33
+ }
34
+
35
+ severities = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
36
+ findings: List[str] = []
37
+
38
+ # ------------------------------------------------------------------
39
+ # 1. Parse the "Key Findings / Key Findings Summary" section
40
+ # This is the most reliable source of the counts.
41
+ # We support:
42
+ # - "### Key Findings Summary:"
43
+ # - "Key Findings Summary:"
44
+ # - bullets with or without bold, e.g.
45
+ # - **Critical Vulnerabilities:** 0
46
+ # - Critical Vulnerabilities: 0
47
+ # - list bullets "-", "*", "•"
48
+ # ------------------------------------------------------------------
49
+ summary_pattern = (
50
+ r"(?:^|\n)#{0,3}\s*Key Findings(?: Summary)?\s*:?(.*?)(?:\n#{1,6}\s|\Z)"
51
+ )
52
+ summary_match = re.search(summary_pattern, report_md, re.DOTALL | re.IGNORECASE)
53
+
54
+ if summary_match:
55
+ summary_text = summary_match.group(1)
56
+
57
+ # Primary patterns (compatible with old bolded markdown + tables)
58
+ primary_patterns = {
59
+ "critical": r"(?:-|\*|•|\|)\s*\*{0,2}Critical(?: Vulnerabilities)?\s*:?\*{0,2}\s*(?::|\|)?\s*(\d+)",
60
+ "high": r"(?:-|\*|•|\|)\s*\*{0,2}High(?: Severity(?: Issues| Vulnerabilities)?)?\s*:?\*{0,2}\s*(?::|\|)?\s*(\d+)",
61
+ "medium": r"(?:-|\*|•|\|)\s*\*{0,2}Medium(?: Severity(?: Issues| Vulnerabilities)?)?\s*:?\*{0,2}\s*(?::|\|)?\s*(\d+)",
62
+ "low": r"(?:-|\*|•|\|)\s*\*{0,2}Low(?: Severity(?: Issues| Vulnerabilities)?)?\s*:?\*{0,2}\s*(?::|\|)?\s*(\d+)",
63
+ "info": r"(?:-|\*|•|\|)\s*\*{0,2}Informational(?: Issues)?\s*:?\*{0,2}\s*(?::|\|)?\s*(\d+)",
64
+ }
65
+
66
+ for severity, pattern in primary_patterns.items():
67
+ match = re.search(pattern, summary_text, re.IGNORECASE)
68
+ if match:
69
+ severities[severity] = int(match.group(1))
70
+
71
+ # Fallback: simple "Label: N" lines (no bullets, no bold)
72
+ if sum(severities.values()) == 0:
73
+ fallback_patterns = {
74
+ "critical": r"Critical Vulnerabilities:\s*(\d+)",
75
+ "high": r"High Severity Vulnerabilities:\s*(\d+)",
76
+ "medium": r"Medium Severity Vulnerabilities:\s*(\d+)",
77
+ "low": r"Low Severity Vulnerabilities:\s*(\d+)",
78
+ "info": r"Informational Issues:\s*(\d+)",
79
+ }
80
+ for severity, pattern in fallback_patterns.items():
81
+ match = re.search(pattern, summary_text, re.IGNORECASE)
82
+ if match:
83
+ severities[severity] = int(match.group(1))
84
+
85
+ # ------------------------------------------------------------------
86
+ # 2. Extract specific findings (titles) from headings
87
+ # ------------------------------------------------------------------
88
+
89
+ # Pattern A: "### Finding X: Title" or "### 4.1 Finding X: Title"
90
+ pattern_a = r"###\s+(?:\d+\.\d+\s+)?Finding\s+\d+\s*:\s*(.+?)(?:\n|$)"
91
+ matches_a = re.findall(pattern_a, report_md, re.IGNORECASE)
92
+
93
+ finding_headers: List[str] = []
94
+ for title in matches_a:
95
+ finding_headers.append(title.strip())
96
+
97
+ # Pattern B: "### X.X SEVERITY: Title"
98
+ pattern_b = (
99
+ r"###\s+(?:\d+\.\d+\s+)?(CRITICAL|HIGH|MEDIUM|LOW|INFO)\s*:\s*(.+?)(?:\n|$)"
100
+ )
101
+ matches_b = re.findall(pattern_b, report_md, re.IGNORECASE)
102
+ for severity, title in matches_b:
103
+ finding_headers.append(f"[{severity.upper()}] {title.strip()}")
104
+
105
+ # Deduplicate and clean up
106
+ seen = set()
107
+ for f in finding_headers:
108
+ if f not in seen:
109
+ findings.append(f)
110
+ seen.add(f)
111
+
112
+ # If still no findings, use a loose heading-based heuristic
113
+ if not findings:
114
+ loose_pattern = r"####?\s+(.+?)(?:\n|$)"
115
+ potential_loose = re.findall(loose_pattern, report_md)
116
+ ignore_terms = [
117
+ "summary",
118
+ "methodology",
119
+ "specification",
120
+ "recommendation",
121
+ "conclusion",
122
+ "table of contents",
123
+ "risk matrix",
124
+ "description",
125
+ "impact",
126
+ "evidence",
127
+ "steps to reproduce",
128
+ "affected endpoints",
129
+ "related cwe",
130
+ "missing headers",
131
+ "test execution",
132
+ "compliance",
133
+ "appendix",
134
+ "additional endpoints",
135
+ "response codes",
136
+ ]
137
+ for finding in potential_loose:
138
+ if not any(x in finding.lower() for x in ignore_terms):
139
+ findings.append(finding.strip())
140
+
141
+ total = sum(severities.values())
142
+
143
+ return {
144
+ "severities": severities,
145
+ "total": total,
146
+ "findings": findings[:10], # Top 10 findings
147
+ }
148
+
149
+
150
+ def calculate_risk_score(severities: Dict[str, int]) -> int:
151
+ """Calculate overall risk score based on vulnerability severities.
152
+
153
+ Args:
154
+ severities: Dictionary with counts per severity level
155
+
156
+ Returns:
157
+ Risk score from 0-100
158
+ """
159
+ weights = {
160
+ "critical": 25,
161
+ "high": 15,
162
+ "medium": 8,
163
+ "low": 3,
164
+ "info": 1,
165
+ }
166
+
167
+ score = sum(
168
+ count * weights.get(sev.lower(), 0) for sev, count in severities.items()
169
+ )
170
+
171
+ # Cap at 100
172
+ return min(score, 100)
173
+
174
+
175
+ def create_severity_chart(severities: Dict[str, int]) -> go.Figure:
176
+ """Create a pie chart showing vulnerability distribution by severity.
177
+
178
+ Args:
179
+ severities: Dictionary with counts per severity level
180
+
181
+ Returns:
182
+ Plotly figure object
183
+ """
184
+ # Filter out zero counts
185
+ filtered_sev = {k: v for k, v in severities.items() if v > 0}
186
+
187
+ if not filtered_sev:
188
+ # No vulnerabilities found - show placeholder
189
+ fig = go.Figure(
190
+ data=[
191
+ go.Pie(
192
+ labels=["No Vulnerabilities"],
193
+ values=[1],
194
+ marker=dict(colors=["#28a745"]),
195
+ hole=0.4,
196
+ )
197
+ ]
198
+ )
199
+ fig.update_layout(
200
+ title="Vulnerability Distribution",
201
+ annotations=[
202
+ dict(
203
+ text="All Clear!",
204
+ x=0.5,
205
+ y=0.5,
206
+ font_size=20,
207
+ showarrow=False,
208
+ )
209
+ ],
210
+ )
211
+ return fig
212
+
213
+ # Define colors for each severity
214
+ colors = {
215
+ "critical": "#dc3545", # Red
216
+ "high": "#fd7e14", # Orange
217
+ "medium": "#ffc107", # Yellow
218
+ "low": "#17a2b8", # Blue
219
+ "info": "#6c757d", # Gray
220
+ }
221
+
222
+ labels = [k.capitalize() for k in filtered_sev.keys()]
223
+ values = list(filtered_sev.values())
224
+ pie_colors = [colors.get(k, "#6c757d") for k in filtered_sev.keys()]
225
+
226
+ fig = go.Figure(
227
+ data=[
228
+ go.Pie(
229
+ labels=labels,
230
+ values=values,
231
+ marker=dict(colors=pie_colors),
232
+ hole=0.4,
233
+ textinfo="label+value",
234
+ textfont_size=14,
235
+ )
236
+ ]
237
+ )
238
+
239
+ fig.update_layout(
240
+ title={
241
+ "text": "Vulnerability Distribution by Severity",
242
+ "x": 0.5,
243
+ "xanchor": "center",
244
+ },
245
+ height=400,
246
+ showlegend=True,
247
+ legend=dict(
248
+ orientation="v",
249
+ yanchor="middle",
250
+ y=0.5,
251
+ xanchor="left",
252
+ x=1.02,
253
+ ),
254
+ )
255
+
256
+ return fig
257
+
258
+
259
+ def create_risk_gauge(risk_score: int) -> go.Figure:
260
+ """Create a gauge chart showing the risk score.
261
+
262
+ Args:
263
+ risk_score: Risk score from 0-100
264
+
265
+ Returns:
266
+ Plotly figure object
267
+ """
268
+ # Determine color based on risk level
269
+ if risk_score < 20:
270
+ color = "#28a745" # Green
271
+ level = "Low"
272
+ elif risk_score < 40:
273
+ color = "#17a2b8" # Blue
274
+ level = "Moderate"
275
+ elif risk_score < 60:
276
+ color = "#ffc107" # Yellow
277
+ level = "Elevated"
278
+ elif risk_score < 80:
279
+ color = "#fd7e14" # Orange
280
+ level = "High"
281
+ else:
282
+ color = "#dc3545" # Red
283
+ level = "Critical"
284
+
285
+ fig = go.Figure(
286
+ go.Indicator(
287
+ mode="gauge+number+delta",
288
+ value=risk_score,
289
+ title={"text": f"Risk Score: {level}"},
290
+ delta={"reference": 50},
291
+ gauge={
292
+ "axis": {"range": [0, 100]},
293
+ "bar": {"color": color},
294
+ "steps": [
295
+ {"range": [0, 20], "color": "rgba(40, 167, 69, 0.2)"},
296
+ {"range": [20, 40], "color": "rgba(23, 162, 184, 0.2)"},
297
+ {"range": [40, 60], "color": "rgba(255, 193, 7, 0.2)"},
298
+ {"range": [60, 80], "color": "rgba(253, 126, 20, 0.2)"},
299
+ {"range": [80, 100], "color": "rgba(220, 53, 69, 0.2)"},
300
+ ],
301
+ "threshold": {
302
+ "line": {"color": "red", "width": 4},
303
+ "thickness": 0.75,
304
+ "value": 80,
305
+ },
306
+ },
307
+ )
308
+ )
309
+
310
+ # Give the figure some breathing room so nothing hugs the edges / looks clipped
311
+ fig.update_layout(
312
+ height=300,
313
+ margin=dict(l=40, r=40, t=80, b=30),
314
+ autosize=True,
315
+ )
316
+
317
+ return fig
prompt.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Prompt definitions for the VAPT Agent.
3
+ """
4
+
5
+ SYSTEM_PROMPT = """
6
+ You are a security testing expert specializing in API vulnerability assessment and penetration testing (VAPT).
7
+
8
+ Your responsibilities:
9
+ 1. Use the Postman MCP server to automatically create API specifications and test the API.
10
+ 2. Use the vapt_security_test tool to perform comprehensive security testing.
11
+ 3. Analyze vulnerabilities and provide detailed remediation guidance.
12
+ 4. Generate comprehensive security reports in Markdown format within MAX 2500 words.
13
+
14
+ Testing approach:
15
+ - Start by fully understanding the API endpoint structure.
16
+ - Use Postman MCP tools to discover endpoints, parameters, request/response bodies, authentication schemes, and error responses.
17
+ - Generate an API specification (OpenAPI-like or detailed endpoint table) using Postman MCP tools.
18
+ - Run VAPT security tests covering injection, authentication/authorization, rate limiting, CORS, security headers, and SSL/TLS.
19
+
20
+ Reporting rules (very important):
21
+ - All results must be written into a single Markdown file using the Write tool.
22
+ - The report MUST be self-contained: a reader must understand the API and its risks without opening Postman or any external tool.
23
+ - Never say "I created an API spec in Postman" without also documenting it in the report.
24
+
25
+ When you are given an endpoint and headers, you MUST:
26
+
27
+ 1. Create API Specification (via Postman MCP)
28
+ - Use Postman MCP tools to:
29
+ - Explore the given endpoint.
30
+ - Discover available methods, paths, query parameters, headers, and auth schemes.
31
+ - Identify typical request and response bodies, including error responses.
32
+ - Build a structured specification (OpenAPI-like or a detailed endpoint table).
33
+ - When writing the report, include a dedicated section:
34
+
35
+ ## 2. API Specification
36
+
37
+ - Overview of the API.
38
+ - For each endpoint:
39
+ - Method and URL
40
+ - Description
41
+ - Path/query parameters (name, type, required, description)
42
+ - Headers (especially auth-related)
43
+ - Request body schema (if applicable)
44
+ - Response codes and example bodies
45
+
46
+ - Paste the actual specification details into this section; do NOT just describe that a spec exists.
47
+
48
+ 2. Run VAPT Tests (via vapt_security_test tool)
49
+ - Call the vapt_security_test MCP tool with appropriate test types:
50
+ - SQL injection
51
+ - XSS
52
+ - Authentication/authorization issues
53
+ - Rate limiting
54
+ - CORS policy
55
+ - Security headers
56
+ - SSL/TLS configuration (as applicable)
57
+ - Carefully review the JSON results returned by the tool.
58
+
59
+ 3. Write the Markdown Report to File
60
+ - Use the Write tool to create a file named `vapt_report_{timestamp}.md`.
61
+ - The report MUST follow this structure:
62
+
63
+ # VAPT Report
64
+
65
+ ## 1. Executive Summary
66
+ - High-level overview and key risks.
67
+
68
+ ### Key Findings Summary:
69
+ - **Critical Vulnerabilities:** [Count]
70
+ - **High Severity Vulnerabilities:** [Count]
71
+ - **Medium Severity Vulnerabilities:** [Count]
72
+ - **Low Severity Vulnerabilities:** [Count]
73
+ - **Informational Issues:** [Count]
74
+
75
+ ## 2. API Specification
76
+ - (Paste the full spec you built using Postman MCP, as described above.)
77
+
78
+ ## 3. Test Methodology
79
+ - Which tools were used (Postman MCP, vapt_security_test).
80
+ - What types of tests were run.
81
+
82
+ ## 4. Detailed Findings
83
+ - One subsection per vulnerability, including:
84
+ - Title
85
+ - Severity (Critical/High/Medium/Low/Info)
86
+ - Impact
87
+ - Evidence (requests/responses, payloads, headers)
88
+ - Steps to reproduce
89
+ - Affected endpoints
90
+
91
+ ## 5. Recommendations
92
+ - Concrete remediation steps for each issue.
93
+ - Hardening / best-practice guidance.
94
+
95
+ ## 6. Conclusion
96
+
97
+ - Ensure that the **API Specification** section is non-empty and accurately reflects what you discovered using Postman MCP.
98
+
99
+ 4. Summarize Critical/High Issues
100
+ - After writing the report file, provide a short summary in the chat focusing only on Critical and High severity issues.
101
+ """.strip()
102
+
103
+
104
+ def get_vapt_query(
105
+ api_endpoint: str, method: str, headers_str: str, timestamp: str
106
+ ) -> str:
107
+ """
108
+ Generate the VAPT assessment query.
109
+
110
+ Args:
111
+ api_endpoint: The API endpoint to test
112
+ method: HTTP method
113
+ headers_str: JSON string of headers
114
+ timestamp: Timestamp string for the report filename
115
+
116
+ Returns:
117
+ The formatted query string
118
+ """
119
+ return f"""Please perform a comprehensive security assessment of the following API endpoint:
120
+
121
+ Endpoint: {api_endpoint}
122
+ Method: {method}
123
+ Headers: {headers_str}
124
+
125
+ Tasks:
126
+ 1. First, use Postman MCP tools to create an API specification for this endpoint
127
+ 2. Then, use the vapt_security_test tool to perform security testing
128
+ 3. Test for: SQL injection, XSS, authentication issues, rate limiting, CORS policy, security headers, and SSL configuration
129
+ 4. Analyze all findings and create a detailed security report
130
+ 5. Save the report to a file named './vapt_report_{timestamp}.md' in the current working directory (use ./ prefix)
131
+ 6. The report MUST be in Markdown format and include:
132
+ - The full API specification generated in step 1
133
+ - Detailed security assessment findings from step 2
134
+ - Vulnerability analysis and recommendations
135
+
136
+ Provide a summary of critical and high-severity vulnerabilities found."""
137
+
138
+
139
+ def get_tutor_system_prompt(report_snippets: str, include_web: bool) -> str:
140
+ """
141
+ Build the system prompt for the AI tutor.
142
+
143
+ Args:
144
+ report_snippets: Text retrieved from the VAPT report
145
+ include_web: Whether web search snippets are available
146
+
147
+ Returns:
148
+ System prompt string
149
+ """
150
+ web_text = (
151
+ "You may also see a section titled 'Web search snippets'. "
152
+ "Use these only for general security best practices or background, "
153
+ "not for details specific to this particular API implementation.\n\n"
154
+ if include_web
155
+ else "You do NOT have web search snippets for this question; "
156
+ "answer using the VAPT report and your general security knowledge.\n\n"
157
+ )
158
+
159
+ return f"""You are a friendly and knowledgeable security tutor helping developers
160
+ understand API vulnerabilities and security best practices.
161
+
162
+ Your goals:
163
+ - Explain concepts in simple, beginner-friendly terms.
164
+ - Use analogies and real-world examples when helpful.
165
+ - Provide concrete remediation steps and best practices.
166
+ - Stay encouraging and educational.
167
+
168
+ Primary knowledge source:
169
+ - The VAPT report excerpts that will be included in the conversation context.
170
+ Treat these as ground truth for anything specific to THIS API or system.
171
+
172
+ Additional knowledge source:
173
+ - General security knowledge you already have as a model.
174
+ - {web_text.strip()}
175
+
176
+ Formatting rules (CRITICAL):
177
+ - **NEVER use Markdown tables** - they are difficult to read in the chat UI.
178
+ - Instead of tables, use:
179
+ * Short paragraphs with clear topic sentences
180
+ * Bulleted lists for multiple items
181
+ * Numbered lists for sequential steps
182
+ * Section headings (##, ###) to organize content
183
+ - Write in a conversational, flowing style with natural paragraphs.
184
+ - Keep responses skimmable: avoid long walls of text.
185
+ - If you need to present multiple related pieces of information, use
186
+ descriptive bullet points rather than table rows.
187
+
188
+ Answering rules:
189
+ 1. When explaining a vulnerability, break it down into:
190
+ - What it is (short definition)
191
+ - Why it's dangerous (impact)
192
+ - How to fix it (practical remediation steps)
193
+ - How to prevent it (best practices going forward)
194
+
195
+ 2. When the question clearly refers to something in the VAPT report,
196
+ ground your explanation directly in that report content (quote or
197
+ paraphrase the relevant finding where helpful).
198
+
199
+ 3. When the user asks general security questions, you may rely on your
200
+ general knowledge, and (if provided) the web search snippets.
201
+
202
+ 4. Keep responses concise but comprehensive (roughly 150–300 words),
203
+ unless the question explicitly asks for more detail.
204
+
205
+ 5. Include simple code or configuration examples where genuinely helpful
206
+ (e.g., secure headers, parameterized queries, CSP examples).
207
+
208
+ 6. Always be supportive – security is complex, and the user is learning.
209
+
210
+ Current VAPT report focus (excerpt preview, for your reference only):
211
+ \"\"\"
212
+ {report_snippets[:1000]}
213
+ \"\"\""""
requirements.txt ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # VAPT Agent Requirements
2
+
3
+ # Core dependencies
4
+ python-dotenv
5
+
6
+ # Claude Agent SDK
7
+ claude-agent-sdk
8
+
9
+ # HTTP client for security testing
10
+ aiohttp
11
+ aiofiles
12
+
13
+ # Data validation
14
+ pydantic
15
+
16
+ # AWS SDK (for Bedrock support)
17
+ boto3
18
+ botocore
19
+
20
+ # Optional: Logging and monitoring
21
+ colorlog
22
+
23
+ # Optional: For enhanced security testing
24
+ requests
25
+ urllib3
26
+
27
+ # Optional: For report generation
28
+ jinja2
29
+
30
+ gradio
31
+
32
+ # Dashboard and AI Tutor features
33
+ plotly
34
+ anthropic
35
+
36
+ openai
37
+ chromadb
38
+ requests
vapt_agent.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ VAPT Agent - Main module for API security testing.
3
+
4
+ This module orchestrates the vulnerability assessment and penetration testing
5
+ of APIs using Claude Agent SDK with Postman MCP server and custom VAPT tools.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ import json
11
+ from dotenv import load_dotenv
12
+ from pathlib import Path
13
+ from typing import Dict, Optional, Callable, Tuple
14
+ from datetime import datetime
15
+
16
+ load_dotenv(override=True)
17
+
18
+ from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
19
+ from vapt_tools import create_vapt_mcp_server
20
+ from config import VAPTConfig
21
+ from prompt import SYSTEM_PROMPT, get_vapt_query
22
+
23
+
24
+ async def run_vapt_agent_with_callback(
25
+ api_endpoint: str,
26
+ method: str = "GET",
27
+ headers: Dict[str, str] = None,
28
+ working_directory: str = None,
29
+ progress_callback: Optional[Callable[[str], None]] = None,
30
+ ) -> Tuple[str, Optional[str]]:
31
+ """
32
+ Execute VAPT agent with progress callbacks for UI integration.
33
+
34
+ Args:
35
+ api_endpoint: The API endpoint to test
36
+ method: HTTP method for the endpoint
37
+ headers: Optional headers for API requests
38
+ working_directory: Working directory for the agent
39
+ progress_callback: Optional callback function to receive progress updates
40
+
41
+ Returns:
42
+ Tuple of (report_content, report_file_path)
43
+ """
44
+
45
+ config = VAPTConfig()
46
+
47
+ # Progress update helper
48
+ def update_progress(message: str):
49
+ if progress_callback:
50
+ progress_callback(message)
51
+ else:
52
+ print(message)
53
+
54
+ # Set up AWS Bedrock configuration if enabled
55
+ if config.use_bedrock:
56
+ update_progress("🔧 Using AWS Bedrock for Claude")
57
+ os.environ["CLAUDE_CODE_USE_BEDROCK"] = "1"
58
+ else:
59
+ update_progress("🔧 Using Anthropic API for Claude")
60
+
61
+ # Set up Postman MCP server configuration (SSE-based)
62
+ update_progress("🔌 Connecting to Postman MCP server...")
63
+ postman_api_key = config.postman_api_key
64
+ if not postman_api_key:
65
+ raise ValueError("POSTMAN_API_KEY not found in environment variables")
66
+
67
+ postman_mcp_config = {
68
+ "type": "sse",
69
+ "url": "https://mcp.postman.com/mcp",
70
+ "headers": {"Authorization": f"Bearer {postman_api_key}"},
71
+ }
72
+
73
+ # Create custom VAPT MCP server
74
+ update_progress("🛠️ Initializing VAPT security tools...")
75
+ vapt_tool_server = create_vapt_mcp_server()
76
+
77
+ # Configure Claude Agent options
78
+ model_name = config.model_name
79
+
80
+ options = ClaudeAgentOptions(
81
+ system_prompt=SYSTEM_PROMPT,
82
+ mcp_servers={
83
+ "postman": postman_mcp_config,
84
+ "VAPTToolServer": vapt_tool_server,
85
+ },
86
+ allowed_tools=[
87
+ "Read",
88
+ "Write",
89
+ "Bash",
90
+ "Edit",
91
+ "Glob",
92
+ "Grep",
93
+ "WebFetch",
94
+ "WebSearch",
95
+ "mcp__postman__*", # All Postman MCP tools
96
+ "mcp__VAPTToolServer__vapt_security_test",
97
+ ],
98
+ max_turns=100,
99
+ model=model_name,
100
+ permission_mode="bypassPermissions",
101
+ cwd=Path(working_directory) if working_directory else Path.cwd(),
102
+ )
103
+
104
+ report_content = ""
105
+ report_file_path = None
106
+
107
+ async with ClaudeSDKClient(options=options) as client:
108
+ update_progress(f"✅ Connected to Claude Agent SDK ")
109
+ update_progress(f"🎯 Testing endpoint: {api_endpoint}")
110
+
111
+ # Construct the query for the agent
112
+ headers_str = json.dumps(headers, indent=2) if headers else "None"
113
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
114
+
115
+ query = get_vapt_query(api_endpoint, method, headers_str, timestamp)
116
+
117
+ # Execute the query
118
+ timeout_sec = 600 # 10 minutes for security testing
119
+
120
+ update_progress("🔍 Starting security assessment...")
121
+
122
+ try:
123
+ await asyncio.wait_for(client.query(query), timeout=timeout_sec)
124
+ except asyncio.TimeoutError:
125
+ update_progress(f"⏱️ Query timed out after {timeout_sec}s")
126
+ raise
127
+ except Exception as e:
128
+ update_progress(f"❌ Query failed: {str(e)}")
129
+ raise
130
+
131
+ # Stream and collect responses
132
+ update_progress("📊 Collecting security test results...")
133
+
134
+ response_texts = []
135
+ async for message in client.receive_response():
136
+ if hasattr(message, "content"):
137
+ for block in message.content:
138
+ if hasattr(block, "text") and block.text:
139
+ response_texts.append(block.text)
140
+ # Stream progress for tool usage messages
141
+ if "SQL injection" in block.text.lower():
142
+ update_progress(
143
+ "🛡️ Testing SQL injection vulnerabilities..."
144
+ )
145
+ elif "xss" in block.text.lower():
146
+ update_progress("🛡️ Testing XSS vulnerabilities...")
147
+ elif (
148
+ "authentication" in block.text.lower()
149
+ or "authorization" in block.text.lower()
150
+ ):
151
+ update_progress(
152
+ "🔐 Testing authentication/authorization..."
153
+ )
154
+ elif "rate limit" in block.text.lower():
155
+ update_progress("⚡ Testing rate limiting...")
156
+ elif "cors" in block.text.lower():
157
+ update_progress("🌐 Testing CORS policy...")
158
+ elif "headers" in block.text.lower():
159
+ update_progress("🔒 Checking security headers...")
160
+
161
+ report_content = "\n".join(response_texts)
162
+
163
+ # Try to find the generated report file
164
+ update_progress("📄 Locating generated report file...")
165
+ reports_dir = Path.cwd() / "reports"
166
+ if reports_dir.exists():
167
+ # Find the most recent report file
168
+ report_files = list(reports_dir.glob(f"vapt_report_{timestamp[:8]}*.md"))
169
+ if report_files:
170
+ report_file_path = str(
171
+ max(report_files, key=lambda p: p.stat().st_mtime)
172
+ )
173
+ update_progress(f"✅ Report saved: {Path(report_file_path).name}")
174
+ # Read the report content
175
+ with open(report_file_path, "r", encoding="utf-8") as f:
176
+ report_content = f.read()
177
+
178
+ if not report_file_path:
179
+ # Check current directory
180
+ report_files = list(Path.cwd().glob(f"vapt_report_{timestamp}*.md"))
181
+ if report_files:
182
+ report_file_path = str(report_files[0])
183
+ update_progress(f"✅ Report saved: {Path(report_file_path).name}")
184
+ with open(report_file_path, "r", encoding="utf-8") as f:
185
+ report_content = f.read()
186
+
187
+ update_progress("🎉 Security assessment completed!")
188
+
189
+ return report_content, report_file_path
190
+
191
+
192
+ async def run_vapt_agent(
193
+ api_endpoint: str,
194
+ method: str = "GET",
195
+ headers: Dict[str, str] = None,
196
+ working_directory: str = None,
197
+ ) -> None:
198
+ """
199
+ Execute VAPT agent with Postman MCP server and custom security testing tools.
200
+
201
+ Args:
202
+ api_endpoint: The API endpoint to test
203
+ method: HTTP method for the endpoint
204
+ headers: Optional headers for API requests
205
+ working_directory: Working directory for the agent
206
+ """
207
+
208
+ config = VAPTConfig()
209
+
210
+ # Set up AWS Bedrock configuration if enabled
211
+ if config.use_bedrock:
212
+ print("[VAPT Agent] Using AWS Bedrock for Claude")
213
+ os.environ["CLAUDE_CODE_USE_BEDROCK"] = "1"
214
+
215
+ # Set up Postman MCP server configuration (SSE-based)
216
+ postman_api_key = config.postman_api_key
217
+ if not postman_api_key:
218
+ raise ValueError("POSTMAN_API_KEY not found in environment variables")
219
+
220
+ postman_mcp_config = {
221
+ "type": "sse",
222
+ "url": "https://mcp.postman.com/mcp",
223
+ "headers": {"Authorization": f"Bearer {postman_api_key}"},
224
+ }
225
+
226
+ # Create custom VAPT MCP server
227
+ vapt_tool_server = create_vapt_mcp_server()
228
+
229
+ # Configure Claude Agent options
230
+ model_name = config.model_name
231
+
232
+ options = ClaudeAgentOptions(
233
+ system_prompt=SYSTEM_PROMPT,
234
+ mcp_servers={
235
+ "postman": postman_mcp_config,
236
+ "VAPTToolServer": vapt_tool_server,
237
+ },
238
+ allowed_tools=[
239
+ "Read",
240
+ "Write",
241
+ "Bash",
242
+ "Edit",
243
+ "Glob",
244
+ "Grep",
245
+ "WebFetch",
246
+ "WebSearch",
247
+ "mcp__postman__*", # All Postman MCP tools
248
+ "mcp__VAPTToolServer__vapt_security_test",
249
+ ],
250
+ max_turns=100,
251
+ model=model_name,
252
+ permission_mode="bypassPermissions",
253
+ cwd=Path(working_directory) if working_directory else Path.cwd(),
254
+ )
255
+
256
+ async with ClaudeSDKClient(options=options) as client:
257
+ print(f"[VAPT Agent] Connected to Claude Agent SDK")
258
+ if config.use_bedrock:
259
+ print(f"[VAPT Agent] Using AWS Bedrock with model: {model_name}")
260
+ print(f"[VAPT Agent] AWS Region: {config.aws_region}")
261
+ else:
262
+ print(f"[VAPT Agent] Using Anthropic API with model: {model_name}")
263
+ print(f"[VAPT Agent] Testing endpoint: {api_endpoint}")
264
+
265
+ # Construct the query for the agent
266
+ headers_str = json.dumps(headers, indent=2) if headers else "None"
267
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
268
+
269
+ query = get_vapt_query(api_endpoint, method, headers_str, timestamp)
270
+
271
+ # Execute the query
272
+ timeout_sec = 600 # 10 minutes for security testing
273
+
274
+ try:
275
+ await asyncio.wait_for(client.query(query), timeout=timeout_sec)
276
+ except asyncio.TimeoutError:
277
+ print(f"[VAPT Agent] Query timed out after {timeout_sec}s")
278
+ raise
279
+ except Exception as e:
280
+ print(f"[VAPT Agent] Query failed: {str(e)}")
281
+ raise
282
+
283
+ # Stream and print responses
284
+ print("\n[VAPT Agent] Security Testing Results:\n")
285
+ print("=" * 80)
286
+
287
+ async for message in client.receive_response():
288
+ if hasattr(message, "content"):
289
+ for block in message.content:
290
+ if hasattr(block, "text") and block.text:
291
+ print(block.text)
292
+
293
+ print("\n" + "=" * 80)
294
+ print("[VAPT Agent] Security assessment completed")
295
+
296
+
297
+ def main():
298
+ """Main entry point for VAPT agent."""
299
+
300
+ config = VAPTConfig()
301
+
302
+ # Get test configuration
303
+ api_endpoint = config.test_api_endpoint
304
+ method = config.test_api_method
305
+
306
+ headers = {"Content-Type": "application/json", "User-Agent": "VAPT-Agent/1.0"}
307
+
308
+ # Add authentication header if provided
309
+ if config.test_api_key:
310
+ headers["Authorization"] = f"Bearer {config.test_api_key}"
311
+
312
+ print("=" * 80)
313
+ print("VAPT Agent - API Security Testing")
314
+ print("=" * 80)
315
+ if config.use_bedrock:
316
+ print(f"Provider: AWS Bedrock")
317
+ print(f"Region: {config.aws_region}")
318
+ print(f"Model: {config.model_name}")
319
+ else:
320
+ print(f"Provider: Anthropic API")
321
+ print(f"Model: {config.model_name}")
322
+ print(f"Endpoint: {api_endpoint}")
323
+ print(f"Method: {method}")
324
+ print("=" * 80)
325
+ print()
326
+
327
+ try:
328
+ asyncio.run(
329
+ run_vapt_agent(
330
+ api_endpoint=api_endpoint,
331
+ method=method,
332
+ headers=headers,
333
+ )
334
+ )
335
+ except KeyboardInterrupt:
336
+ print("\n[VAPT Agent] Interrupted by user")
337
+ except Exception as e:
338
+ print(f"\n[VAPT Agent] Error: {e}")
339
+ raise
340
+
341
+
342
+ if __name__ == "__main__":
343
+ main()
vapt_styles.css ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global background + text color */
2
+ :root {
3
+ --color-background-primary: #f5f5fb;
4
+ --color-background-secondary: #ffffff;
5
+ --background-fill-primary: #f5f5fb;
6
+ --background-fill-secondary: #ffffff;
7
+ --block-background-fill: #ffffff;
8
+ }
9
+
10
+ html,
11
+ body {
12
+ background: #f5f5fb;
13
+ color: #111827;
14
+ margin: 0;
15
+ }
16
+
17
+ .gradio-container {
18
+ max-width: 1200px !important;
19
+ /* change to 100% for full-width */
20
+ margin: 0 auto !important;
21
+ padding-inline: 24px;
22
+ background: transparent !important;
23
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24
+ }
25
+
26
+ /* Kill default grey on generic panels/boxes */
27
+ .gr-block,
28
+ .gr-box,
29
+ .gr-panel {
30
+ background: transparent !important;
31
+ }
32
+
33
+ /* Main section cards (API Config + Results) */
34
+ .section-card,
35
+ #config-card,
36
+ #results-card {
37
+ background: #ffffff !important;
38
+ /* force white, remove grey header */
39
+ border-radius: 12px;
40
+ padding: 16px 18px;
41
+ box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
42
+ border: 1px solid #e5e7eb;
43
+ }
44
+
45
+ .section-card h3,
46
+ .section-card h4 {
47
+ margin-top: 0;
48
+ color: #111827;
49
+ }
50
+
51
+ #app-header {
52
+ margin-bottom: 1.25rem;
53
+ }
54
+
55
+ #app-header h1 {
56
+ font-size: 2.2rem;
57
+ margin-bottom: 0.25rem;
58
+ color: #0f172a;
59
+ }
60
+
61
+ #app-header p {
62
+ color: #4b5563;
63
+ /* darker, more readable */
64
+ font-size: 0.95rem;
65
+ margin-top: 0.25rem;
66
+ }
67
+
68
+ .badge-pill {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ gap: 0.4rem;
72
+ padding: 0.25rem 0.85rem;
73
+ border-radius: 999px;
74
+ font-size: 0.9rem;
75
+ /* bigger badge text */
76
+ font-weight: 600;
77
+ background: #ecfdf3;
78
+ color: #15803d;
79
+ border: 1px solid #bbf7d0;
80
+ }
81
+
82
+ .badge-pill .dot {
83
+ width: 9px;
84
+ height: 9px;
85
+ border-radius: 999px;
86
+ background: #22c55e;
87
+ box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.25);
88
+ }
89
+
90
+ #disclaimer-box {
91
+ background: #fefce8;
92
+ border-left: 4px solid #f59e0b;
93
+ padding: 10px 12px;
94
+ border-radius: 10px;
95
+ font-size: 0.86rem;
96
+ margin-top: 0.5rem;
97
+ color: #92400e;
98
+ }
99
+
100
+ #disclaimer-box strong {
101
+ color: #b45309;
102
+ }
103
+
104
+ #config-card>div>label span,
105
+ #results-card>div>label span {
106
+ font-weight: 500;
107
+ color: #111827;
108
+ }
109
+
110
+ /* Reduce font size inside the Security Tutor chatbot */
111
+ #tutor-chat * {
112
+ font-size: 0.80rem !important;
113
+ }
114
+
115
+ #tutor-chat .message {
116
+ padding-top: 4px;
117
+ padding-bottom: 4px;
118
+ }
119
+
120
+ /* Reset button styling – elem_id is on the button itself */
121
+ #reset-btn {
122
+ background-color: #e0f2fe !important;
123
+ /* light blue */
124
+ color: #0369a1 !important;
125
+ border-color: #bae6fd !important;
126
+ }
127
+
128
+ #reset-btn:hover {
129
+ background-color: #bfdbfe !important;
130
+ }
131
+
132
+ /* --- Professional full-width tabs for Security Assessment Results --- */
133
+
134
+ /* Tab bar container */
135
+ #results-card [role="tablist"] {
136
+ background: #ffffff !important;
137
+ /* ensure no grey behind tabs */
138
+ border-radius: 8px;
139
+ border: 1px solid #e5e7eb;
140
+ display: flex;
141
+ padding: 0;
142
+ margin-top: 4px;
143
+ overflow: hidden;
144
+ }
145
+
146
+ /* Each tab button */
147
+ #results-card [role="tablist"] button {
148
+ flex: 1 1 0;
149
+ justify-content: center;
150
+ background: #ffffff !important;
151
+ color: #374151;
152
+ border-radius: 0;
153
+ border: none;
154
+ box-shadow: none;
155
+ font-size: 0.9rem;
156
+ padding-block: 0.4rem;
157
+ padding-inline: 0.75rem;
158
+ }
159
+
160
+ /* Hover state for inactive tabs */
161
+ #results-card [role="tablist"] button:hover:not([aria-selected="true"]) {
162
+ background: #f3f4f6;
163
+ }
164
+
165
+ /* Active tab (selected) */
166
+ #results-card [role="tablist"] button[aria-selected="true"] {
167
+ background: #ffffff !important;
168
+ color: #ea580c !important;
169
+ /* orange accent */
170
+ font-weight: 600;
171
+ box-shadow: inset 0 -3px 0 #ea580c;
172
+ /* bottom border highlight */
173
+ }
174
+
175
+ /* Ensure tab content area is white */
176
+ #results-card .tabitem {
177
+ background: #ffffff !important;
178
+ }
179
+
180
+ /* --- Security Tutor panel styling --- */
181
+
182
+ #tutor-section {
183
+ margin-top: 6px;
184
+ gap: 10px;
185
+ }
186
+
187
+ /* Input row card */
188
+ #tutor-input-row {
189
+ background: #f9fafb;
190
+ border-radius: 12px;
191
+ padding: 10px 12px;
192
+ border: 1px solid #e5e7eb;
193
+ align-items: stretch;
194
+ }
195
+
196
+ /* Text area inside tutor row */
197
+ #tutor-input-row textarea {
198
+ min-height: 64px;
199
+ border-radius: 8px 0 0 8px !important;
200
+ }
201
+
202
+ /* Ask button styled as attached to textbox */
203
+ #tutor-input-row button {
204
+ height: 100%;
205
+ border-radius: 0 8px 8px 0 !important;
206
+ padding-inline: 1.6rem;
207
+ font-weight: 600;
208
+ }
209
+
210
+ /* Example box */
211
+ #tutor-examples {
212
+ margin-top: 10px;
213
+ background: #f9fafb;
214
+ border-radius: 10px;
215
+ padding: 10px 12px;
216
+ border: 1px solid #e5e7eb;
217
+ font-size: 0.86rem;
218
+ }
219
+
220
+ #tutor-examples strong {
221
+ display: block;
222
+ margin-bottom: 4px;
223
+ color: #111827;
224
+ }
225
+
226
+ #tutor-examples ul {
227
+ margin: 0;
228
+ padding-left: 1.1rem;
229
+ }
230
+
231
+ /* Security Report markdown container */
232
+ #security-report-md {
233
+ background: #ffffff !important;
234
+ border-radius: 8px;
235
+ padding: 16px 20px;
236
+ border: 1px solid #e5e7eb;
237
+ max-height: 520px;
238
+ /* adjust as you like */
239
+ overflow-y: auto;
240
+ /* vertical scroll when content exceeds height */
241
+ overflow-x: hidden;
242
+ /* avoid horizontal bar unless you need it */
243
+ }
244
+
245
+ /* Inner markdown/prose should be transparent, no extra padding/border */
246
+ #security-report-md .gr-markdown,
247
+ #security-report-md .prose {
248
+ background: transparent !important;
249
+ border: none !important;
250
+ box-shadow: none !important;
251
+ padding: 0 !important;
252
+ }
253
+
254
+ /* Center the two dashboard charts horizontally */
255
+ #dashboard-row {
256
+ justify-content: center;
257
+ gap: 24px;
258
+ }
259
+
260
+ #risk-col,
261
+ #severity-col {
262
+ display: flex;
263
+ justify-content: center;
264
+ }
265
+
266
+ #risk-col .gr-plot,
267
+ #severity-col .gr-plot {
268
+ max-width: 480px;
269
+ width: 100%;
270
+ }