mirror of
https://github.com/mblanke/StrikePackageGPT.git
synced 2026-03-01 06:10:21 -05:00
feat: Add HackGpt Enterprise features
- 6-Phase pentest methodology UI (Recon, Scanning, Vuln, Exploit, Report, Retest) - Phase-aware AI prompts with context from current phase - Attack chain analysis and visualization - CVSS-style severity badges (CRITICAL/HIGH/MEDIUM/LOW) - Findings sidebar with severity counts - Phase-specific tools and quick actions
This commit is contained in:
1
.devcontainer/devcontainer.json
Normal file
1
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Environment Configuration for StrikePackageGPT
|
||||||
|
# Copy this file to .env and fill in your values
|
||||||
|
|
||||||
|
# LLM API Keys (optional - Ollama works without API keys)
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
ANTHROPIC_API_KEY=
|
||||||
|
|
||||||
|
# Ollama Configuration
|
||||||
|
OLLAMA_BASE_URL=http://ollama:11434
|
||||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Data directories (may contain sensitive info)
|
||||||
|
data/
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
220
Claude.md
Normal file
220
Claude.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# StrikePackageGPT - Development Guidelines
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
StrikePackageGPT is an AI-powered security analysis platform combining LLM capabilities with professional penetration testing tools. It provides a web interface for security researchers and penetration testers to interact with AI assistants specialized in cybersecurity.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Dashboard (8080) │
|
||||||
|
│ FastAPI + Jinja2 Templates │
|
||||||
|
│ Tabbed UI: Chat | Terminal | Scans │
|
||||||
|
└──────────────────────────┬──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────▼──────────────────────────────────────┐
|
||||||
|
│ HackGPT API (8001) │
|
||||||
|
│ Security-focused API endpoints │
|
||||||
|
│ Chat, Scans, Execute, Analyze, AI-Scan │
|
||||||
|
└───────────────┬─────────────────────────┬───────────────────────┘
|
||||||
|
│ │
|
||||||
|
┌───────────────▼──────────┐ ┌───────────▼───────────────────────┐
|
||||||
|
│ LLM Router (8000) │ │ Kali Executor (8002) │
|
||||||
|
│ OpenAI/Anthropic/Ollama │ │ Docker SDK command execution │
|
||||||
|
└───────────────┬──────────┘ └───────────┬───────────────────────┘
|
||||||
|
│ │
|
||||||
|
┌───────────────▼──────────┐ ┌───────────▼───────────────────────┐
|
||||||
|
│ Ollama (11434) │ │ Kali Container │
|
||||||
|
│ Local LLM inference │ │ nmap, nikto, sqlmap, etc. │
|
||||||
|
└──────────────────────────┘ └───────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Shared Library │
|
||||||
|
│ models.py | parsers.py | tools.py │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
1. **Chat Flow**: User → Dashboard → HackGPT API → LLM Router → Ollama/OpenAI/Anthropic
|
||||||
|
2. **Scan Flow**: User → Dashboard → HackGPT API → Kali Executor → Kali Container
|
||||||
|
3. **AI-Scan Flow**: Combines both - AI plans the scan, Executor runs it, AI analyzes results
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
### Dashboard (`services/dashboard/`)
|
||||||
|
- **Port**: 8080
|
||||||
|
- **Purpose**: Web UI for interacting with the security AI
|
||||||
|
- **Tech**: FastAPI, Jinja2, TailwindCSS, Alpine.js
|
||||||
|
- **Key files**:
|
||||||
|
- `app/main.py` - FastAPI application
|
||||||
|
- `templates/index.html` - Main dashboard UI
|
||||||
|
|
||||||
|
### HackGPT API (`services/hackgpt-api/`)
|
||||||
|
- **Port**: 8001
|
||||||
|
- **Purpose**: Security-focused API with specialized prompts
|
||||||
|
- **Tech**: FastAPI
|
||||||
|
- **Key endpoints**:
|
||||||
|
- `POST /chat` - Security chat interface with session support
|
||||||
|
- `POST /analyze` - Start async security analysis tasks
|
||||||
|
- `POST /execute` - Execute commands in Kali (proxied)
|
||||||
|
- `POST /scan` - Run security scans (nmap, nikto, etc.)
|
||||||
|
- `POST /ai-scan` - AI-driven intelligent scanning
|
||||||
|
- `GET /scans` - List all scans with status
|
||||||
|
- `GET /tools` - List available security tools
|
||||||
|
- `POST /suggest-command` - AI-suggested security commands
|
||||||
|
|
||||||
|
### LLM Router (`services/llm-router/`)
|
||||||
|
- **Port**: 8000
|
||||||
|
- **Purpose**: Route requests to different LLM providers
|
||||||
|
- **Tech**: FastAPI, httpx
|
||||||
|
- **Supported providers**:
|
||||||
|
- OpenAI (gpt-4o, gpt-4o-mini)
|
||||||
|
- Anthropic (Claude Sonnet 4, Claude 3.5 Haiku)
|
||||||
|
- Ollama (local models - llama3.2, codellama, mistral)
|
||||||
|
|
||||||
|
### Kali (`services/kali/`)
|
||||||
|
- **Purpose**: Container with security tools
|
||||||
|
- **Base**: kalilinux/kali-rolling
|
||||||
|
- **Tools included**:
|
||||||
|
- Reconnaissance: nmap, masscan, amass, theHarvester
|
||||||
|
- Web: nikto, gobuster, sqlmap
|
||||||
|
- Exploitation: metasploit, hydra, searchsploit
|
||||||
|
|
||||||
|
### Kali Executor (`services/kali-executor/`)
|
||||||
|
- **Port**: 8002
|
||||||
|
- **Purpose**: Execute commands in the Kali container via Docker SDK
|
||||||
|
- **Tech**: FastAPI, Docker SDK, WebSockets
|
||||||
|
- **Key endpoints**:
|
||||||
|
- `POST /execute` - Execute a command (with whitelist validation)
|
||||||
|
- `WS /stream` - WebSocket for real-time command output
|
||||||
|
- `GET /jobs` - List running/completed jobs
|
||||||
|
- `GET /tools` - List available security tools
|
||||||
|
- **Security**: Command whitelist restricts executable binaries
|
||||||
|
|
||||||
|
### Shared Library (`services/shared/`)
|
||||||
|
- **Purpose**: Common models and utilities shared across services
|
||||||
|
- **Files**:
|
||||||
|
- `models.py` - Pydantic models (ScanResult, CommandResult, etc.)
|
||||||
|
- `parsers.py` - Output parsers for nmap, nikto, etc.
|
||||||
|
- `tools.py` - Security tool definitions and templates
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Start with build
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# View specific service logs
|
||||||
|
docker-compose logs -f dashboard
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Rebuild specific service
|
||||||
|
docker-compose build dashboard
|
||||||
|
|
||||||
|
# Access Kali container
|
||||||
|
docker exec -it strikepackage-kali bash
|
||||||
|
|
||||||
|
# Pull Ollama model (run after first start)
|
||||||
|
docker exec -it strikepackage-ollama ollama pull llama3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` and configure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Optional - Ollama works without these
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
|
||||||
|
# Ollama (default works for Docker network)
|
||||||
|
OLLAMA_BASE_URL=http://ollama:11434
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Python
|
||||||
|
- Use type hints for all function parameters and returns
|
||||||
|
- Use Pydantic models for request/response validation
|
||||||
|
- Async functions for I/O operations
|
||||||
|
- Follow PEP 8 naming conventions
|
||||||
|
- Use docstrings for public functions
|
||||||
|
|
||||||
|
### API Design
|
||||||
|
- RESTful endpoints with clear naming
|
||||||
|
- Health check endpoint at `/health` for each service
|
||||||
|
- Consistent error responses with HTTPException
|
||||||
|
- CORS enabled for all services
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- Alpine.js for reactivity
|
||||||
|
- TailwindCSS for styling
|
||||||
|
- Marked.js for Markdown rendering
|
||||||
|
- Responsive design
|
||||||
|
|
||||||
|
## Adding New Features
|
||||||
|
|
||||||
|
### New LLM Provider
|
||||||
|
1. Add configuration to `LLM_ROUTER_URL` in `llm-router/app/main.py`
|
||||||
|
2. Implement `_call_<provider>` async function
|
||||||
|
3. Add provider to `/providers` endpoint
|
||||||
|
4. Update `ChatRequest` model if needed
|
||||||
|
|
||||||
|
### New Security Tool
|
||||||
|
1. Install in `kali/Dockerfile`
|
||||||
|
2. Add to tool list in `hackgpt-api/app/main.py` `/tools` endpoint
|
||||||
|
3. Add relevant prompts to `SECURITY_PROMPTS` dict
|
||||||
|
|
||||||
|
### New Analysis Type
|
||||||
|
1. Add prompt to `SECURITY_PROMPTS` in `hackgpt-api/app/main.py`
|
||||||
|
2. Add button in `dashboard/templates/index.html`
|
||||||
|
3. Update `SecurityAnalysisRequest` model if needed
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- This platform is for **authorized security testing only**
|
||||||
|
- Always obtain proper authorization before testing
|
||||||
|
- The Kali container has elevated network capabilities
|
||||||
|
- API keys are passed via environment variables
|
||||||
|
- No authentication is implemented by default (add for production)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Ollama not responding
|
||||||
|
```bash
|
||||||
|
# Check if Ollama is running
|
||||||
|
docker-compose logs ollama
|
||||||
|
|
||||||
|
# Pull a model if none exist
|
||||||
|
docker exec -it strikepackage-ollama ollama pull llama3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service connection issues
|
||||||
|
```bash
|
||||||
|
# Check network
|
||||||
|
docker network ls
|
||||||
|
docker network inspect strikepackagegpt_strikepackage-net
|
||||||
|
|
||||||
|
# Check service health
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
curl http://localhost:8001/health
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build issues
|
||||||
|
```bash
|
||||||
|
# Clean rebuild
|
||||||
|
docker-compose down
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
151
README.md
151
README.md
@@ -1 +1,150 @@
|
|||||||
# StrikePackageGPT
|
# ⚡ StrikePackageGPT
|
||||||
|
|
||||||
|
AI-powered security analysis platform combining LLM capabilities with professional penetration testing tools.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 🎯 Overview
|
||||||
|
|
||||||
|
StrikePackageGPT provides security researchers and penetration testers with an AI assistant specialized in:
|
||||||
|
|
||||||
|
- **Reconnaissance** - OSINT, subdomain enumeration, port scanning strategies
|
||||||
|
- **Vulnerability Analysis** - CVE research, misconfiguration detection
|
||||||
|
- **Exploit Research** - Safe research and documentation of exploits
|
||||||
|
- **Report Generation** - Professional security assessment reports
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- 8GB+ RAM recommended (for local LLM)
|
||||||
|
- (Optional) OpenAI or Anthropic API key
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/mblanke/StrikePackageGPT.git
|
||||||
|
cd StrikePackageGPT
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure environment** (optional)
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env to add API keys if using cloud LLMs
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start the services**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Pull a local model** (first time only)
|
||||||
|
```bash
|
||||||
|
docker exec -it strikepackage-ollama ollama pull llama3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Access the dashboard**
|
||||||
|
|
||||||
|
Open http://localhost:8080 in your browser
|
||||||
|
|
||||||
|
## 📦 Services
|
||||||
|
|
||||||
|
| Service | Port | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| Dashboard | 8080 | Web UI with Chat, Terminal, and Scans tabs |
|
||||||
|
| HackGPT API | 8001 | Security-focused API with scan management |
|
||||||
|
| Kali Executor | 8002 | Docker SDK command execution |
|
||||||
|
| LLM Router | 8000 | Multi-provider LLM gateway |
|
||||||
|
| Ollama | 11434 | Local LLM inference |
|
||||||
|
| Kali | - | Security tools container |
|
||||||
|
|
||||||
|
## 🛠️ Security Tools
|
||||||
|
|
||||||
|
The Kali container includes:
|
||||||
|
|
||||||
|
- **Reconnaissance**: nmap, masscan, amass, theHarvester, whatweb
|
||||||
|
- **Web Testing**: nikto, gobuster, dirb, sqlmap
|
||||||
|
- **Exploitation**: metasploit-framework, hydra, searchsploit
|
||||||
|
- **Network**: tcpdump, netcat, wireshark
|
||||||
|
|
||||||
|
Access the Kali container:
|
||||||
|
```bash
|
||||||
|
docker exec -it strikepackage-kali bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤖 LLM Providers
|
||||||
|
|
||||||
|
StrikePackageGPT supports multiple LLM providers:
|
||||||
|
|
||||||
|
| Provider | Models | API Key Required |
|
||||||
|
|----------|--------|------------------|
|
||||||
|
| Ollama | llama3.2, codellama, mistral | No (local) |
|
||||||
|
| OpenAI | gpt-4o, gpt-4o-mini | Yes |
|
||||||
|
| Anthropic | claude-sonnet-4-20250514, claude-3-5-haiku | Yes |
|
||||||
|
|
||||||
|
## 📖 Usage Examples
|
||||||
|
|
||||||
|
### Chat with the AI
|
||||||
|
Ask security-related questions in natural language:
|
||||||
|
- "Explain how to use nmap for service detection"
|
||||||
|
- "What are common web application vulnerabilities?"
|
||||||
|
- "How do I enumerate subdomains for a target?"
|
||||||
|
|
||||||
|
### Terminal Access
|
||||||
|
Execute commands directly in the Kali container from the Terminal tab:
|
||||||
|
- Real-time command output
|
||||||
|
- Command history with up/down arrows
|
||||||
|
- Whitelisted tools for security
|
||||||
|
|
||||||
|
### Security Scans
|
||||||
|
Launch and monitor scans from the Scans tab:
|
||||||
|
- **nmap** - Port scanning and service detection
|
||||||
|
- **nikto** - Web server vulnerability scanning
|
||||||
|
- **gobuster** - Directory and DNS enumeration
|
||||||
|
- **sqlmap** - SQL injection testing
|
||||||
|
- **whatweb** - Web technology fingerprinting
|
||||||
|
|
||||||
|
### Quick Analysis
|
||||||
|
Use the sidebar buttons to start guided analysis:
|
||||||
|
- 🔍 **Reconnaissance** - Plan your information gathering
|
||||||
|
- 🛡️ **Vulnerability Scan** - Assess potential weaknesses
|
||||||
|
- 💉 **Exploit Research** - Research known vulnerabilities
|
||||||
|
- 📄 **Generate Report** - Create professional documentation
|
||||||
|
|
||||||
|
## ⚠️ Legal Disclaimer
|
||||||
|
|
||||||
|
This tool is intended for **authorized security testing only**. Always:
|
||||||
|
|
||||||
|
- Obtain written permission before testing any systems
|
||||||
|
- Follow responsible disclosure practices
|
||||||
|
- Comply with all applicable laws and regulations
|
||||||
|
- Use in isolated lab environments when learning
|
||||||
|
|
||||||
|
The developers are not responsible for misuse of this software.
|
||||||
|
|
||||||
|
## 🔧 Development
|
||||||
|
|
||||||
|
See [Claude.md](./Claude.md) for development guidelines.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild after changes
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT License - See [LICENSE](./LICENSE) for details.
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions welcome! Please read the development guidelines in Claude.md before submitting PRs.
|
||||||
118
docker-compose.yml
Normal file
118
docker-compose.yml
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
services:
|
||||||
|
# Web Dashboard - Main user interface
|
||||||
|
dashboard:
|
||||||
|
build:
|
||||||
|
context: ./services/dashboard
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: strikepackage-dashboard
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- HACKGPT_API_URL=http://strikepackage-hackgpt-api:8001
|
||||||
|
- LLM_ROUTER_URL=http://strikepackage-llm-router:8000
|
||||||
|
- KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002
|
||||||
|
depends_on:
|
||||||
|
- hackgpt-api
|
||||||
|
- llm-router
|
||||||
|
networks:
|
||||||
|
- strikepackage-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# HackGPT API - Security-focused API service
|
||||||
|
hackgpt-api:
|
||||||
|
build:
|
||||||
|
context: ./services/hackgpt-api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: strikepackage-hackgpt-api
|
||||||
|
ports:
|
||||||
|
- "8001:8001"
|
||||||
|
environment:
|
||||||
|
- LLM_ROUTER_URL=http://strikepackage-llm-router:8000
|
||||||
|
- KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002
|
||||||
|
depends_on:
|
||||||
|
- llm-router
|
||||||
|
- kali-executor
|
||||||
|
networks:
|
||||||
|
- strikepackage-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Kali Executor - Command execution service
|
||||||
|
kali-executor:
|
||||||
|
build:
|
||||||
|
context: ./services/kali-executor
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: strikepackage-kali-executor
|
||||||
|
ports:
|
||||||
|
- "8002:8002"
|
||||||
|
environment:
|
||||||
|
- KALI_CONTAINER_NAME=strikepackage-kali
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
depends_on:
|
||||||
|
- kali
|
||||||
|
networks:
|
||||||
|
- strikepackage-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# LLM Router - Routes to different LLM providers
|
||||||
|
llm-router:
|
||||||
|
build:
|
||||||
|
context: ./services/llm-router
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: strikepackage-llm-router
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||||
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||||
|
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://192.168.1.50:11434}
|
||||||
|
networks:
|
||||||
|
- strikepackage-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Kali Linux - Security tools container
|
||||||
|
kali:
|
||||||
|
build:
|
||||||
|
context: ./services/kali
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: strikepackage-kali
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
volumes:
|
||||||
|
- kali-workspace:/workspace
|
||||||
|
- ./data:/data
|
||||||
|
networks:
|
||||||
|
- strikepackage-net
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- NET_RAW
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Ollama - Local LLM (disabled - using Dell LLM box at 192.168.1.50)
|
||||||
|
# Uncomment to use local Ollama instead
|
||||||
|
# ollama:
|
||||||
|
# image: ollama/ollama:latest
|
||||||
|
# container_name: strikepackage-ollama
|
||||||
|
# ports:
|
||||||
|
# - "11434:11434"
|
||||||
|
# volumes:
|
||||||
|
# - ollama-models:/root/.ollama
|
||||||
|
# networks:
|
||||||
|
# - strikepackage-net
|
||||||
|
# restart: unless-stopped
|
||||||
|
# # Uncomment for GPU support:
|
||||||
|
# # deploy:
|
||||||
|
# # resources:
|
||||||
|
# # reservations:
|
||||||
|
# # devices:
|
||||||
|
# # - driver: nvidia
|
||||||
|
# # count: all
|
||||||
|
# # capabilities: [gpu]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
strikepackage-net:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kali-workspace:
|
||||||
|
ollama-models:
|
||||||
84
scripts/init.sh
Normal file
84
scripts/init.sh
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "=================================================="
|
||||||
|
echo " StrikePackageGPT - Initialization Script"
|
||||||
|
echo "=================================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for Docker
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "❌ Docker is not installed. Please install Docker first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
echo "❌ Docker Compose is not installed. Please install Docker Compose first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Docker and Docker Compose are installed"
|
||||||
|
|
||||||
|
# Create .env if not exists
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
echo "📄 Creating .env file from template..."
|
||||||
|
cp .env.example .env
|
||||||
|
echo "✅ Created .env file. Edit it to add API keys if needed."
|
||||||
|
else
|
||||||
|
echo "✅ .env file already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create data directory
|
||||||
|
mkdir -p data
|
||||||
|
echo "✅ Created data directory"
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Starting services..."
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Wait for services to be ready
|
||||||
|
echo ""
|
||||||
|
echo "⏳ Waiting for services to start..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Check service health
|
||||||
|
echo ""
|
||||||
|
echo "🔍 Checking service health..."
|
||||||
|
|
||||||
|
check_service() {
|
||||||
|
local url=$1
|
||||||
|
local name=$2
|
||||||
|
if curl -s "$url" > /dev/null 2>&1; then
|
||||||
|
echo " ✅ $name is healthy"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ⏳ $name is starting..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_service "http://localhost:8000/health" "LLM Router"
|
||||||
|
check_service "http://localhost:8001/health" "HackGPT API"
|
||||||
|
check_service "http://localhost:8080/health" "Dashboard"
|
||||||
|
|
||||||
|
# Pull default Ollama model
|
||||||
|
echo ""
|
||||||
|
echo "📥 Pulling default LLM model (llama3.2)..."
|
||||||
|
echo " This may take a few minutes on first run..."
|
||||||
|
docker exec strikepackage-ollama ollama pull llama3.2
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=================================================="
|
||||||
|
echo " ✅ StrikePackageGPT is ready!"
|
||||||
|
echo "=================================================="
|
||||||
|
echo ""
|
||||||
|
echo " Dashboard: http://localhost:8080"
|
||||||
|
echo " API Docs: http://localhost:8001/docs"
|
||||||
|
echo " LLM Router: http://localhost:8000/docs"
|
||||||
|
echo ""
|
||||||
|
echo " To access Kali container:"
|
||||||
|
echo " docker exec -it strikepackage-kali bash"
|
||||||
|
echo ""
|
||||||
|
echo " To view logs:"
|
||||||
|
echo " docker-compose logs -f"
|
||||||
|
echo ""
|
||||||
88
scripts/preflight.ps1
Normal file
88
scripts/preflight.ps1
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# StrikePackageGPT Pre-flight Check
|
||||||
|
|
||||||
|
$AllPassed = $true
|
||||||
|
|
||||||
|
function Show-Check {
|
||||||
|
param([string]$Name, [bool]$Passed, [string]$Message)
|
||||||
|
if ($Passed) {
|
||||||
|
Write-Host " [OK] $Name" -ForegroundColor Green
|
||||||
|
if ($Message) { Write-Host " $Message" -ForegroundColor DarkGray }
|
||||||
|
} else {
|
||||||
|
Write-Host " [X] $Name" -ForegroundColor Red
|
||||||
|
if ($Message) { Write-Host " $Message" -ForegroundColor Yellow }
|
||||||
|
$script:AllPassed = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " StrikePackageGPT Pre-flight Check " -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Hardware
|
||||||
|
Write-Host " HARDWARE" -ForegroundColor White
|
||||||
|
Write-Host " --------" -ForegroundColor DarkGray
|
||||||
|
$os = Get-CimInstance Win32_OperatingSystem
|
||||||
|
$ram = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
|
||||||
|
Show-Check "RAM: ${ram}GB" ($ram -ge 8) ""
|
||||||
|
$disk = [math]::Round((Get-PSDrive (Get-Location).Drive.Name).Free / 1GB, 1)
|
||||||
|
Show-Check "Disk: ${disk}GB free" ($disk -ge 20) ""
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Write-Host " DOCKER" -ForegroundColor White
|
||||||
|
Write-Host " ------" -ForegroundColor DarkGray
|
||||||
|
$dockerOk = $null -ne (Get-Command docker -ErrorAction SilentlyContinue)
|
||||||
|
Show-Check "Docker installed" $dockerOk ""
|
||||||
|
$dockerRun = $false
|
||||||
|
if ($dockerOk) { try { docker info 2>$null | Out-Null; $dockerRun = $true } catch {} }
|
||||||
|
Show-Check "Docker running" $dockerRun ""
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Containers
|
||||||
|
Write-Host " CONTAINERS" -ForegroundColor White
|
||||||
|
Write-Host " ----------" -ForegroundColor DarkGray
|
||||||
|
if ($dockerRun) {
|
||||||
|
$containers = @("dashboard","hackgpt-api","llm-router","kali","kali-executor")
|
||||||
|
foreach ($c in $containers) {
|
||||||
|
$name = "strikepackage-$c"
|
||||||
|
$status = docker ps --filter "name=$name" --format "{{.Status}}" 2>$null
|
||||||
|
if ($status) {
|
||||||
|
Show-Check $name $true $status
|
||||||
|
} else {
|
||||||
|
Write-Host " [ ] $name - Not running" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Ollama
|
||||||
|
Write-Host " OLLAMA" -ForegroundColor White
|
||||||
|
Write-Host " ------" -ForegroundColor DarkGray
|
||||||
|
try {
|
||||||
|
$r = Invoke-RestMethod -Uri "http://localhost:11434/api/tags" -TimeoutSec 3 -ErrorAction Stop
|
||||||
|
$m = ($r.models | ForEach-Object { $_.name }) -join ", "
|
||||||
|
Show-Check "Local Ollama" $true $m
|
||||||
|
} catch {
|
||||||
|
Write-Host " [ ] Local Ollama - Not running" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$r = Invoke-RestMethod -Uri "http://192.168.1.50:11434/api/tags" -TimeoutSec 3 -ErrorAction Stop
|
||||||
|
$m = ($r.models | ForEach-Object { $_.name }) -join ", "
|
||||||
|
Show-Check "Dell LLM Box" $true $m
|
||||||
|
} catch {
|
||||||
|
Write-Host " [ ] Dell LLM Box - Not reachable" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================================" -ForegroundColor DarkGray
|
||||||
|
if ($AllPassed) {
|
||||||
|
Write-Host " ALL CHECKS PASSED!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " Some checks failed" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
18
services/dashboard/Dockerfile
Normal file
18
services/dashboard/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app/ ./app/
|
||||||
|
COPY templates/ ./templates/
|
||||||
|
COPY static/ ./static/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||||
0
services/dashboard/app/__init__.py
Normal file
0
services/dashboard/app/__init__.py
Normal file
368
services/dashboard/app/main.py
Normal file
368
services/dashboard/app/main.py
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
"""
|
||||||
|
StrikePackageGPT Dashboard
|
||||||
|
Web interface for security analysis and LLM-powered penetration testing assistant.
|
||||||
|
"""
|
||||||
|
from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="StrikePackageGPT Dashboard",
|
||||||
|
description="Web interface for AI-powered security analysis",
|
||||||
|
version="0.2.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
HACKGPT_API_URL = os.getenv("HACKGPT_API_URL", "http://strikepackage-hackgpt-api:8001")
|
||||||
|
LLM_ROUTER_URL = os.getenv("LLM_ROUTER_URL", "http://strikepackage-llm-router:8000")
|
||||||
|
KALI_EXECUTOR_URL = os.getenv("KALI_EXECUTOR_URL", "http://strikepackage-kali-executor:8002")
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMessage(BaseModel):
|
||||||
|
message: str
|
||||||
|
session_id: Optional[str] = None
|
||||||
|
provider: str = "ollama"
|
||||||
|
model: str = "llama3.2"
|
||||||
|
context: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class PhaseChatMessage(BaseModel):
|
||||||
|
message: str
|
||||||
|
phase: str
|
||||||
|
provider: str = "ollama"
|
||||||
|
model: str = "llama3.2"
|
||||||
|
findings: List[Dict[str, Any]] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class AttackChainRequest(BaseModel):
|
||||||
|
findings: List[Dict[str, Any]]
|
||||||
|
provider: str = "ollama"
|
||||||
|
model: str = "llama3.2"
|
||||||
|
|
||||||
|
|
||||||
|
class CommandRequest(BaseModel):
|
||||||
|
command: str
|
||||||
|
timeout: int = Field(default=300, ge=1, le=3600)
|
||||||
|
working_dir: str = "/workspace"
|
||||||
|
|
||||||
|
|
||||||
|
class ScanRequest(BaseModel):
|
||||||
|
tool: str
|
||||||
|
target: str
|
||||||
|
scan_type: Optional[str] = None
|
||||||
|
options: Dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {"status": "healthy", "service": "dashboard"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def index(request: Request):
|
||||||
|
"""Main dashboard page"""
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/terminal", response_class=HTMLResponse)
|
||||||
|
async def terminal_page(request: Request):
|
||||||
|
"""Terminal page"""
|
||||||
|
return templates.TemplateResponse("terminal.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/status")
|
||||||
|
async def get_services_status():
|
||||||
|
"""Get status of all backend services"""
|
||||||
|
services = {}
|
||||||
|
|
||||||
|
service_checks = [
|
||||||
|
("llm-router", f"{LLM_ROUTER_URL}/health"),
|
||||||
|
("hackgpt-api", f"{HACKGPT_API_URL}/health"),
|
||||||
|
("kali-executor", f"{KALI_EXECUTOR_URL}/health"),
|
||||||
|
]
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
for name, url in service_checks:
|
||||||
|
try:
|
||||||
|
response = await client.get(url, timeout=5.0)
|
||||||
|
services[name] = response.status_code == 200
|
||||||
|
except:
|
||||||
|
services[name] = False
|
||||||
|
|
||||||
|
return {"services": services}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/processes")
|
||||||
|
async def get_running_processes():
|
||||||
|
"""Get running security processes in Kali container"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{KALI_EXECUTOR_URL}/processes", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return {"running_processes": [], "count": 0}
|
||||||
|
except:
|
||||||
|
return {"running_processes": [], "count": 0}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/providers")
|
||||||
|
async def get_providers():
|
||||||
|
"""Get available LLM providers"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{LLM_ROUTER_URL}/providers", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail="Failed to get providers")
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="LLM Router not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/tools")
|
||||||
|
async def get_tools():
|
||||||
|
"""Get available security tools"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{HACKGPT_API_URL}/tools", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail="Failed to get tools")
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/chat")
|
||||||
|
async def chat(message: ChatMessage):
|
||||||
|
"""Send chat message to HackGPT API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/chat",
|
||||||
|
json=message.model_dump(),
|
||||||
|
timeout=120.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/chat/phase")
|
||||||
|
async def phase_chat(message: PhaseChatMessage):
|
||||||
|
"""Send phase-aware chat message to HackGPT API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/chat/phase",
|
||||||
|
json=message.model_dump(),
|
||||||
|
timeout=120.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/attack-chains")
|
||||||
|
async def analyze_attack_chains(request: AttackChainRequest):
|
||||||
|
"""Analyze findings to identify attack chains"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/attack-chains",
|
||||||
|
json=request.model_dump(),
|
||||||
|
timeout=120.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/analyze")
|
||||||
|
async def analyze(request: Request):
|
||||||
|
"""Start security analysis"""
|
||||||
|
data = await request.json()
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/analyze",
|
||||||
|
json=data,
|
||||||
|
timeout=30.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/task/{task_id}")
|
||||||
|
async def get_task(task_id: str):
|
||||||
|
"""Get task status"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{HACKGPT_API_URL}/task/{task_id}", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/suggest-command")
|
||||||
|
async def suggest_command(message: ChatMessage):
|
||||||
|
"""Get AI-suggested security commands"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/suggest-command",
|
||||||
|
json=message.model_dump(),
|
||||||
|
timeout=60.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Command Execution ==============
|
||||||
|
|
||||||
|
@app.post("/api/execute")
|
||||||
|
async def execute_command(request: CommandRequest):
|
||||||
|
"""Execute a command in the Kali container"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/execute",
|
||||||
|
json=request.model_dump(),
|
||||||
|
timeout=float(request.timeout + 30)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise HTTPException(status_code=504, detail="Command execution timed out")
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Scan Management ==============
|
||||||
|
|
||||||
|
@app.post("/api/scan")
|
||||||
|
async def start_scan(request: ScanRequest):
|
||||||
|
"""Start a security scan"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/scan",
|
||||||
|
json=request.model_dump(),
|
||||||
|
timeout=30.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/scan/{scan_id}")
|
||||||
|
async def get_scan_result(scan_id: str):
|
||||||
|
"""Get scan results"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{HACKGPT_API_URL}/scan/{scan_id}", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/scans")
|
||||||
|
async def list_scans():
|
||||||
|
"""List all scans"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{HACKGPT_API_URL}/scans", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/ai-scan")
|
||||||
|
async def ai_scan(message: ChatMessage):
|
||||||
|
"""AI-assisted scanning"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{HACKGPT_API_URL}/ai-scan",
|
||||||
|
json=message.model_dump(),
|
||||||
|
timeout=120.0
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="HackGPT API not available")
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Kali Container Info ==============
|
||||||
|
|
||||||
|
@app.get("/api/kali/info")
|
||||||
|
async def get_kali_info():
|
||||||
|
"""Get Kali container information"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{KALI_EXECUTOR_URL}/container/info", timeout=10.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali executor not available")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/kali/tools")
|
||||||
|
async def get_kali_tools():
|
||||||
|
"""Get installed tools in Kali container"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{KALI_EXECUTOR_URL}/tools", timeout=30.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali executor not available")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8080)
|
||||||
5
services/dashboard/requirements.txt
Normal file
5
services/dashboard/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi==0.115.5
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
httpx==0.28.1
|
||||||
|
pydantic==2.10.2
|
||||||
|
jinja2==3.1.4
|
||||||
8
services/dashboard/static/README.md
Normal file
8
services/dashboard/static/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Static Assets
|
||||||
|
|
||||||
|
Place your custom assets here:
|
||||||
|
|
||||||
|
- `icon.png` - Your StrikePackageGPT logo/icon (recommended: 64x64 or 128x128)
|
||||||
|
- `flag.png` - Canadian flag image (recommended: ~32px height)
|
||||||
|
|
||||||
|
These will appear in the dashboard header.
|
||||||
946
services/dashboard/templates/index.html
Normal file
946
services/dashboard/templates/index.html
Normal file
@@ -0,0 +1,946 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>StrikePackageGPT - Security Analysis Dashboard</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<link rel="icon" type="image/png" href="/static/icon.png">
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'sp-black': '#0a0a0a',
|
||||||
|
'sp-dark': '#141414',
|
||||||
|
'sp-grey': '#1f1f1f',
|
||||||
|
'sp-grey-light': '#2a2a2a',
|
||||||
|
'sp-grey-mid': '#3a3a3a',
|
||||||
|
'sp-red': '#dc2626',
|
||||||
|
'sp-red-dark': '#991b1b',
|
||||||
|
'sp-red-light': '#ef4444',
|
||||||
|
'sp-white': '#ffffff',
|
||||||
|
'sp-white-dim': '#e5e5e5',
|
||||||
|
'sp-white-muted': '#a3a3a3',
|
||||||
|
'sev-critical': '#dc2626',
|
||||||
|
'sev-high': '#ea580c',
|
||||||
|
'sev-medium': '#eab308',
|
||||||
|
'sev-low': '#22c55e',
|
||||||
|
'sev-info': '#3b82f6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.chat-container { height: calc(100vh - 320px); }
|
||||||
|
.terminal-container { height: calc(100vh - 280px); }
|
||||||
|
.message-content pre { background: #0a0a0a; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; font-family: monospace; border: 1px solid #2a2a2a; }
|
||||||
|
.message-content code { background: #1f1f1f; padding: 0.2rem 0.4rem; border-radius: 0.25rem; font-family: monospace; color: #ef4444; }
|
||||||
|
.typing-indicator span { animation: blink 1.4s infinite both; }
|
||||||
|
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
||||||
|
@keyframes blink { 0%, 60%, 100% { opacity: 0; } 30% { opacity: 1; } }
|
||||||
|
.terminal-output { font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; font-size: 13px; line-height: 1.5; }
|
||||||
|
.terminal-output .stdout { color: #e5e5e5; }
|
||||||
|
.terminal-output .stderr { color: #ef4444; }
|
||||||
|
.terminal-output .cmd { color: #dc2626; }
|
||||||
|
.scan-card { transition: all 0.2s ease; }
|
||||||
|
.scan-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(220, 38, 38, 0.15); }
|
||||||
|
.glow-red { box-shadow: 0 0 20px rgba(220, 38, 38, 0.3); }
|
||||||
|
@keyframes pulse-red { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||||
|
.pulse-red { animation: pulse-red 2s ease-in-out infinite; }
|
||||||
|
.severity-badge { font-weight: 600; padding: 2px 8px; border-radius: 4px; font-size: 11px; text-transform: uppercase; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-sp-black text-sp-white">
|
||||||
|
<div x-data="dashboard()" x-init="init()" class="min-h-screen flex flex-col">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="bg-sp-dark border-b border-sp-grey-mid px-6 py-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<img src="/static/icon.png" alt="StrikePackageGPT" class="h-10 w-10" onerror="this.style.display='none'">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl font-bold text-sp-red">StrikePackageGPT</h1>
|
||||||
|
<span class="text-xs text-sp-white-muted">AI-Powered Penetration Testing Platform</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src="/static/flag.png" alt="Canada" class="h-6 ml-2" onerror="this.style.display='none'">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div x-show="runningProcesses.length > 0"
|
||||||
|
class="flex items-center gap-2 px-3 py-1.5 rounded bg-sp-red/20 border border-sp-red/50 cursor-pointer"
|
||||||
|
@click="showProcesses = !showProcesses">
|
||||||
|
<span class="w-2 h-2 rounded-full bg-sp-red pulse-red"></span>
|
||||||
|
<span class="text-sp-red text-sm font-medium" x-text="runningProcesses.length + ' Running'"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3 text-sm">
|
||||||
|
<template x-for="(status, service) in services" :key="service">
|
||||||
|
<div class="flex items-center gap-1 px-2 py-1 rounded bg-sp-grey">
|
||||||
|
<span class="w-2 h-2 rounded-full" :class="status ? 'bg-green-500' : 'bg-sp-red'"></span>
|
||||||
|
<span class="text-sp-white-muted text-xs" x-text="service"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<select x-model="selectedProvider" @change="updateModels()"
|
||||||
|
class="bg-sp-grey border border-sp-grey-mid rounded px-3 py-1 text-sm text-sp-white focus:border-sp-red focus:outline-none">
|
||||||
|
<option value="ollama">Ollama (Local)</option>
|
||||||
|
<option value="openai">OpenAI</option>
|
||||||
|
<option value="anthropic">Anthropic</option>
|
||||||
|
</select>
|
||||||
|
<select x-model="selectedModel" class="bg-sp-grey border border-sp-grey-mid rounded px-3 py-1 text-sm text-sp-white focus:border-sp-red focus:outline-none">
|
||||||
|
<template x-for="model in availableModels" :key="model">
|
||||||
|
<option :value="model" x-text="model"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="showProcesses && runningProcesses.length > 0" x-transition
|
||||||
|
class="mt-4 p-3 bg-sp-grey rounded border border-sp-grey-mid">
|
||||||
|
<h4 class="text-sm font-semibold text-sp-red mb-2">🔄 Running Processes</h4>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<template x-for="proc in runningProcesses" :key="proc.pid">
|
||||||
|
<div class="flex items-center justify-between text-xs bg-sp-dark p-2 rounded">
|
||||||
|
<span class="text-sp-white-muted font-mono" x-text="proc.command"></span>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-sp-white-muted">CPU: <span class="text-sp-red" x-text="proc.cpu + '%'"></span></span>
|
||||||
|
<span class="text-sp-white-muted">Time: <span x-text="proc.time"></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 6-Phase Methodology Tabs -->
|
||||||
|
<div class="flex gap-1 mt-4 border-b border-sp-grey-mid">
|
||||||
|
<template x-for="phase in phases" :key="phase.id">
|
||||||
|
<button @click="activePhase = phase.id; activeTab = 'phase'"
|
||||||
|
:class="activePhase === phase.id && activeTab === 'phase' ? 'border-sp-red text-sp-red bg-sp-grey' : 'border-transparent text-sp-white-muted hover:text-white hover:bg-sp-grey-light'"
|
||||||
|
class="px-4 py-2 font-medium transition border-b-2 flex items-center gap-2">
|
||||||
|
<span x-text="phase.icon"></span>
|
||||||
|
<span class="hidden lg:inline" x-text="phase.name"></span>
|
||||||
|
<span class="lg:hidden" x-text="phase.short"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<button @click="activeTab = 'terminal'"
|
||||||
|
:class="activeTab === 'terminal' ? 'border-sp-red text-sp-red bg-sp-grey' : 'border-transparent text-sp-white-muted hover:text-white hover:bg-sp-grey-light'"
|
||||||
|
class="px-4 py-2 font-medium transition border-b-2">
|
||||||
|
🖥️ Terminal
|
||||||
|
</button>
|
||||||
|
<button @click="activeTab = 'attack-chains'"
|
||||||
|
:class="activeTab === 'attack-chains' ? 'border-sp-red text-sp-red bg-sp-grey' : 'border-transparent text-sp-white-muted hover:text-white hover:bg-sp-grey-light'"
|
||||||
|
class="px-4 py-2 font-medium transition border-b-2 flex items-center gap-2">
|
||||||
|
⛓️ Attack Chains
|
||||||
|
<span x-show="attackChains.length > 0" class="px-2 py-0.5 bg-sp-red/30 rounded-full text-xs" x-text="attackChains.length"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="flex flex-1 overflow-hidden">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="w-64 bg-sp-dark border-r border-sp-grey-mid p-4 overflow-y-auto">
|
||||||
|
<div class="mb-4 p-3 bg-sp-grey rounded border border-sp-grey-mid">
|
||||||
|
<h3 class="text-sm font-semibold text-sp-red mb-2" x-text="getCurrentPhase().name"></h3>
|
||||||
|
<p class="text-xs text-sp-white-muted" x-text="getCurrentPhase().description"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-lg font-semibold mb-4 text-sp-white-dim">🛠️ Phase Tools</h2>
|
||||||
|
<div class="space-y-1 mb-6">
|
||||||
|
<template x-for="tool in getCurrentPhase().tools" :key="tool.name">
|
||||||
|
<button @click="askAboutTool(tool)"
|
||||||
|
class="w-full text-left text-sm bg-sp-grey hover:bg-sp-grey-light px-3 py-2 rounded transition">
|
||||||
|
<span class="font-mono text-sp-red-light" x-text="tool.name"></span>
|
||||||
|
<p class="text-xs text-sp-white-muted truncate" x-text="tool.description"></p>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-lg font-semibold mb-4 text-sp-white-dim">⚡ Quick Actions</h2>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<template x-for="action in getCurrentPhase().quickActions" :key="action.label">
|
||||||
|
<button @click="executeQuickAction(action)"
|
||||||
|
class="w-full text-left text-sm bg-sp-grey hover:bg-sp-grey-light hover:border-sp-red border border-transparent px-3 py-2 rounded flex items-center gap-2 transition">
|
||||||
|
<span x-text="action.icon"></span>
|
||||||
|
<span x-text="action.label"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6" x-show="findings.length > 0">
|
||||||
|
<h2 class="text-lg font-semibold mb-4 text-sp-white-dim">📊 Findings</h2>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-sev-critical">Critical</span>
|
||||||
|
<span class="font-bold" x-text="countBySeverity('critical')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-sev-high">High</span>
|
||||||
|
<span class="font-bold" x-text="countBySeverity('high')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-sev-medium">Medium</span>
|
||||||
|
<span class="font-bold" x-text="countBySeverity('medium')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-sev-low">Low</span>
|
||||||
|
<span class="font-bold" x-text="countBySeverity('low')"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Content Area -->
|
||||||
|
<main class="flex-1 flex flex-col">
|
||||||
|
|
||||||
|
<!-- Phase Content -->
|
||||||
|
<div x-show="activeTab === 'phase'" class="flex-1 flex flex-col">
|
||||||
|
<div class="chat-container overflow-y-auto p-6 space-y-4" id="chatContainer">
|
||||||
|
<template x-for="(msg, index) in messages" :key="index">
|
||||||
|
<div :class="msg.role === 'user' ? 'flex justify-end' : 'flex justify-start'">
|
||||||
|
<div :class="msg.role === 'user'
|
||||||
|
? 'bg-sp-red text-white max-w-3xl'
|
||||||
|
: 'bg-sp-grey text-sp-white-dim max-w-4xl border border-sp-grey-mid'"
|
||||||
|
class="rounded-lg px-4 py-3 message-content">
|
||||||
|
<template x-if="msg.role === 'assistant' && msg.phase">
|
||||||
|
<div class="flex items-center gap-2 mb-2 pb-2 border-b border-sp-grey-mid">
|
||||||
|
<span class="text-xs px-2 py-1 bg-sp-red/20 text-sp-red rounded" x-text="msg.phase"></span>
|
||||||
|
<template x-if="msg.risk_score">
|
||||||
|
<span :class="getRiskColor(msg.risk_score)"
|
||||||
|
class="severity-badge"
|
||||||
|
x-text="getRiskLabel(msg.risk_score)"></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-html="renderMarkdown(msg.content)"></div>
|
||||||
|
<template x-if="msg.findings && msg.findings.length > 0">
|
||||||
|
<div class="mt-3 pt-3 border-t border-sp-grey-mid">
|
||||||
|
<h5 class="text-xs font-semibold text-sp-white-muted mb-2">Findings:</h5>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<template x-for="finding in msg.findings" :key="finding.id">
|
||||||
|
<div class="flex items-center gap-2 text-xs bg-sp-black/50 p-2 rounded">
|
||||||
|
<span :class="getSeverityBadgeClass(finding.severity)"
|
||||||
|
class="severity-badge"
|
||||||
|
x-text="finding.severity"></span>
|
||||||
|
<span x-text="finding.title"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div x-show="isLoading" class="flex justify-start">
|
||||||
|
<div class="bg-sp-grey rounded-lg px-4 py-3 border border-sp-grey-mid">
|
||||||
|
<div class="typing-indicator flex gap-1">
|
||||||
|
<span class="w-2 h-2 bg-sp-red rounded-full"></span>
|
||||||
|
<span class="w-2 h-2 bg-sp-red rounded-full"></span>
|
||||||
|
<span class="w-2 h-2 bg-sp-red rounded-full"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="phaseScans.length > 0" class="border-t border-sp-grey-mid bg-sp-dark p-3">
|
||||||
|
<div class="flex items-center gap-4 overflow-x-auto">
|
||||||
|
<span class="text-xs text-sp-white-muted font-semibold">Scans:</span>
|
||||||
|
<template x-for="scan in phaseScans" :key="scan.scan_id">
|
||||||
|
<div @click="viewScanDetails(scan)"
|
||||||
|
class="flex items-center gap-2 px-3 py-1 bg-sp-grey rounded cursor-pointer hover:bg-sp-grey-light">
|
||||||
|
<span :class="{
|
||||||
|
'bg-yellow-500': scan.status === 'running' || scan.status === 'pending',
|
||||||
|
'bg-green-500': scan.status === 'completed',
|
||||||
|
'bg-sp-red': scan.status === 'failed'
|
||||||
|
}" class="w-2 h-2 rounded-full"></span>
|
||||||
|
<span class="text-xs text-sp-red font-mono" x-text="scan.tool"></span>
|
||||||
|
<span class="text-xs text-sp-white-muted" x-text="scan.target"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-sp-grey-mid p-4 bg-sp-dark">
|
||||||
|
<form @submit.prevent="sendMessage()" class="flex gap-4">
|
||||||
|
<input type="text" x-model="userInput"
|
||||||
|
:placeholder="getPhasePrompt()"
|
||||||
|
class="flex-1 bg-sp-grey border border-sp-grey-mid rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-sp-red focus:border-transparent text-sp-white placeholder-sp-white-muted"
|
||||||
|
:disabled="isLoading">
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-sp-red hover:bg-sp-red-dark disabled:bg-sp-grey-mid px-6 py-3 rounded-lg font-semibold transition text-white"
|
||||||
|
:disabled="isLoading || !userInput.trim()">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Terminal Tab -->
|
||||||
|
<div x-show="activeTab === 'terminal'" class="flex-1 flex flex-col">
|
||||||
|
<div class="terminal-container overflow-y-auto p-4 bg-sp-black font-mono" id="terminalOutput">
|
||||||
|
<div class="terminal-output">
|
||||||
|
<template x-for="(line, index) in terminalHistory" :key="index">
|
||||||
|
<div>
|
||||||
|
<template x-if="line.type === 'cmd'">
|
||||||
|
<div class="cmd mb-1">
|
||||||
|
<span class="text-sp-red">kali@strikepackage</span>:<span class="text-sp-white-muted">~</span>$ <span class="text-white" x-text="line.content"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="line.type === 'stdout'">
|
||||||
|
<pre class="stdout whitespace-pre-wrap" x-text="line.content"></pre>
|
||||||
|
</template>
|
||||||
|
<template x-if="line.type === 'stderr'">
|
||||||
|
<pre class="stderr whitespace-pre-wrap" x-text="line.content"></pre>
|
||||||
|
</template>
|
||||||
|
<template x-if="line.type === 'info'">
|
||||||
|
<div class="text-sp-white-muted italic" x-text="line.content"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="terminalLoading" class="text-sp-red animate-pulse">
|
||||||
|
Executing command...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-sp-grey-mid p-4 bg-sp-dark">
|
||||||
|
<form @submit.prevent="executeCommand()" class="flex gap-4">
|
||||||
|
<div class="flex items-center text-sp-red font-mono text-sm">
|
||||||
|
kali@strikepackage:~$
|
||||||
|
</div>
|
||||||
|
<input type="text" x-model="terminalInput"
|
||||||
|
placeholder="Enter command to execute in Kali container..."
|
||||||
|
class="flex-1 bg-sp-black border border-sp-grey-mid rounded px-4 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-sp-red text-sp-white placeholder-sp-white-muted"
|
||||||
|
:disabled="terminalLoading"
|
||||||
|
@keyup.up="historyUp()"
|
||||||
|
@keyup.down="historyDown()">
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-sp-red hover:bg-sp-red-dark disabled:bg-sp-grey-mid px-4 py-2 rounded font-semibold transition text-white"
|
||||||
|
:disabled="terminalLoading || !terminalInput.trim()">
|
||||||
|
Run
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Attack Chains Tab -->
|
||||||
|
<div x-show="activeTab === 'attack-chains'" class="flex-1 overflow-y-auto p-6">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-bold text-sp-white">⛓️ Attack Chain Analysis</h2>
|
||||||
|
<p class="text-sm text-sp-white-muted">Correlated vulnerabilities and potential attack paths</p>
|
||||||
|
</div>
|
||||||
|
<button @click="analyzeAttackChains()"
|
||||||
|
class="bg-sp-red hover:bg-sp-red-dark px-4 py-2 rounded text-sm transition flex items-center gap-2"
|
||||||
|
:disabled="findings.length === 0">
|
||||||
|
🔗 Analyze Chains
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="attackChains.length === 0" class="text-center text-sp-white-muted py-12">
|
||||||
|
<p class="text-4xl mb-4">⛓️</p>
|
||||||
|
<p>No attack chains detected yet.</p>
|
||||||
|
<p class="text-sm mt-2">Complete reconnaissance and vulnerability scanning to identify potential attack paths.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<template x-for="(chain, chainIdx) in attackChains" :key="chainIdx">
|
||||||
|
<div class="bg-sp-dark rounded-lg border border-sp-grey-mid overflow-hidden">
|
||||||
|
<div class="p-4 border-b border-sp-grey-mid flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span :class="getRiskColor(chain.risk_score)"
|
||||||
|
class="severity-badge"
|
||||||
|
x-text="getRiskLabel(chain.risk_score)"></span>
|
||||||
|
<h3 class="font-bold text-sp-white" x-text="chain.name"></h3>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-sp-white-muted">
|
||||||
|
Risk Score: <span class="text-sp-red font-bold" x-text="chain.risk_score.toFixed(1)"></span>/10
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="relative">
|
||||||
|
<template x-for="(step, stepIdx) in chain.steps" :key="stepIdx">
|
||||||
|
<div class="flex items-start gap-4 mb-4 last:mb-0">
|
||||||
|
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-sp-red flex items-center justify-center text-white font-bold text-sm">
|
||||||
|
<span x-text="step.step"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 bg-sp-grey rounded p-3">
|
||||||
|
<div class="font-semibold text-sp-white" x-text="step.action"></div>
|
||||||
|
<div class="text-sm text-sp-white-muted mt-1" x-text="step.method || step.vuln || step.tools"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 pt-4 border-t border-sp-grey-mid">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<span class="text-sp-red">⚠️</span>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-semibold text-sp-white-muted">Impact:</span>
|
||||||
|
<span class="text-sm text-sp-white" x-text="chain.impact"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4 mt-2 text-sm text-sp-white-muted">
|
||||||
|
<span>Likelihood: <span class="text-sp-red" x-text="(chain.likelihood * 100).toFixed(0) + '%'"></span></span>
|
||||||
|
<span x-text="chain.recommendation"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scan Modal -->
|
||||||
|
<div x-show="scanModalOpen" x-transition class="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-sp-dark rounded-lg p-6 w-full max-w-md border border-sp-grey-mid glow-red" @click.away="scanModalOpen = false">
|
||||||
|
<h3 class="text-lg font-bold mb-4 text-sp-white">Start <span class="text-sp-red" x-text="scanModal.tool"></span> Scan</h3>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-sp-white-muted mb-1">Target</label>
|
||||||
|
<input type="text" x-model="scanModal.target"
|
||||||
|
placeholder="IP, hostname, or URL"
|
||||||
|
class="w-full bg-sp-grey border border-sp-grey-mid rounded px-4 py-2 focus:outline-none focus:ring-2 focus:ring-sp-red text-sp-white placeholder-sp-white-muted">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-sp-white-muted mb-1">Scan Type</label>
|
||||||
|
<select x-model="scanModal.scanType"
|
||||||
|
class="w-full bg-sp-grey border border-sp-grey-mid rounded px-4 py-2 text-sp-white focus:outline-none focus:ring-2 focus:ring-sp-red">
|
||||||
|
<template x-for="type in scanModal.types" :key="type">
|
||||||
|
<option :value="type" x-text="type"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 mt-6">
|
||||||
|
<button @click="scanModalOpen = false"
|
||||||
|
class="flex-1 bg-sp-grey hover:bg-sp-grey-light px-4 py-2 rounded transition">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button @click="startScan()"
|
||||||
|
class="flex-1 bg-sp-red hover:bg-sp-red-dark px-4 py-2 rounded font-semibold transition"
|
||||||
|
:disabled="!scanModal.target">
|
||||||
|
Start Scan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scan Details Modal -->
|
||||||
|
<div x-show="detailsModalOpen" x-transition class="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-sp-dark rounded-lg p-6 w-full max-w-4xl max-h-[80vh] overflow-y-auto border border-sp-grey-mid" @click.away="detailsModalOpen = false">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-bold text-sp-white">Scan Details</h3>
|
||||||
|
<button @click="detailsModalOpen = false" class="text-sp-white-muted hover:text-white text-xl">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template x-if="selectedScan">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div><span class="text-sp-white-muted">Tool:</span> <span class="text-sp-red font-bold" x-text="selectedScan.tool"></span></div>
|
||||||
|
<div><span class="text-sp-white-muted">Target:</span> <span class="text-sp-white" x-text="selectedScan.target"></span></div>
|
||||||
|
<div><span class="text-sp-white-muted">Status:</span> <span x-text="selectedScan.status"></span></div>
|
||||||
|
<div><span class="text-sp-white-muted">Started:</span> <span x-text="selectedScan.started_at"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm text-sp-white-muted mb-2">Command</h4>
|
||||||
|
<pre class="bg-sp-black p-3 rounded text-sm overflow-x-auto border border-sp-grey font-mono text-sp-white" x-text="selectedScan.command"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template x-if="selectedScan.parsed && selectedScan.parsed.findings">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm text-sp-white-muted mb-2">Findings (<span x-text="selectedScan.parsed.findings.length"></span>)</h4>
|
||||||
|
<div class="space-y-2 max-h-48 overflow-y-auto">
|
||||||
|
<template x-for="finding in selectedScan.parsed.findings" :key="finding.raw">
|
||||||
|
<div class="flex items-start gap-2 bg-sp-black p-2 rounded text-sm">
|
||||||
|
<span :class="getSeverityBadgeClass(finding.severity)"
|
||||||
|
class="severity-badge flex-shrink-0"
|
||||||
|
x-text="finding.severity"></span>
|
||||||
|
<span class="text-sp-white-dim" x-text="finding.raw"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template x-if="selectedScan.result && selectedScan.result.stdout">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm text-sp-white-muted mb-2">Raw Output</h4>
|
||||||
|
<pre class="bg-sp-black p-3 rounded text-sm overflow-x-auto text-sp-white-dim max-h-96 border border-sp-grey font-mono" x-text="selectedScan.result.stdout"></pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function dashboard() {
|
||||||
|
return {
|
||||||
|
activeTab: 'phase',
|
||||||
|
activePhase: 'recon',
|
||||||
|
messages: [],
|
||||||
|
userInput: '',
|
||||||
|
isLoading: false,
|
||||||
|
services: {},
|
||||||
|
selectedProvider: 'ollama',
|
||||||
|
selectedModel: 'llama3.2',
|
||||||
|
availableModels: ['llama3.2', 'codellama', 'mistral'],
|
||||||
|
providers: {},
|
||||||
|
terminalInput: '',
|
||||||
|
terminalHistory: [],
|
||||||
|
terminalLoading: false,
|
||||||
|
commandHistory: [],
|
||||||
|
historyIndex: -1,
|
||||||
|
scans: [],
|
||||||
|
scanModalOpen: false,
|
||||||
|
scanModal: { tool: '', target: '', scanType: '', types: [] },
|
||||||
|
detailsModalOpen: false,
|
||||||
|
selectedScan: null,
|
||||||
|
runningProcesses: [],
|
||||||
|
showProcesses: false,
|
||||||
|
findings: [],
|
||||||
|
attackChains: [],
|
||||||
|
|
||||||
|
phases: [
|
||||||
|
{
|
||||||
|
id: 'recon',
|
||||||
|
name: 'Reconnaissance',
|
||||||
|
short: 'Recon',
|
||||||
|
icon: '🔍',
|
||||||
|
description: 'Gather information about the target through passive and active reconnaissance techniques.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'nmap', description: 'Network scanner and port enumeration' },
|
||||||
|
{ name: 'theHarvester', description: 'OSINT gathering for emails, subdomains' },
|
||||||
|
{ name: 'amass', description: 'Subdomain enumeration and mapping' },
|
||||||
|
{ name: 'whatweb', description: 'Web technology fingerprinting' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '🎯', label: 'Quick Port Scan', tool: 'nmap', scanType: 'quick' },
|
||||||
|
{ icon: '🔎', label: 'Tech Fingerprint', tool: 'whatweb', scanType: 'default' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'scanning',
|
||||||
|
name: 'Scanning',
|
||||||
|
short: 'Scan',
|
||||||
|
icon: '📡',
|
||||||
|
description: 'Perform in-depth scanning and enumeration of discovered services.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'nmap -sV', description: 'Service version detection' },
|
||||||
|
{ name: 'gobuster', description: 'Directory and file brute-force' },
|
||||||
|
{ name: 'enum4linux', description: 'SMB/Windows enumeration' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '🔬', label: 'Full Port Scan', tool: 'nmap', scanType: 'full' },
|
||||||
|
{ icon: '📁', label: 'Dir Brute Force', tool: 'gobuster', scanType: 'dir' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vuln',
|
||||||
|
name: 'Vulnerability Assessment',
|
||||||
|
short: 'Vuln',
|
||||||
|
icon: '🛡️',
|
||||||
|
description: 'Identify and assess vulnerabilities in discovered services.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'nikto', description: 'Web server vulnerability scanner' },
|
||||||
|
{ name: 'nuclei', description: 'Template-based vuln scanner' },
|
||||||
|
{ name: 'sqlmap', description: 'SQL injection detection' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '🕸️', label: 'Web Vuln Scan', tool: 'nikto', scanType: 'default' },
|
||||||
|
{ icon: '💉', label: 'SQLi Test', tool: 'sqlmap', scanType: 'test' },
|
||||||
|
{ icon: '🔎', label: 'Nmap Vuln Scripts', tool: 'nmap', scanType: 'vuln' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'exploit',
|
||||||
|
name: 'Exploitation',
|
||||||
|
short: 'Exploit',
|
||||||
|
icon: '💥',
|
||||||
|
description: 'Safely exploit verified vulnerabilities to demonstrate impact.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'metasploit', description: 'Exploitation framework' },
|
||||||
|
{ name: 'hydra', description: 'Password brute-force tool' },
|
||||||
|
{ name: 'searchsploit', description: 'Exploit database search' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '🔍', label: 'Search Exploits', prompt: 'Search for exploits for the vulnerabilities we found' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'report',
|
||||||
|
name: 'Reporting',
|
||||||
|
short: 'Report',
|
||||||
|
icon: '📄',
|
||||||
|
description: 'Document findings and create professional security reports.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'Report Generator', description: 'AI-generated executive summary' },
|
||||||
|
{ name: 'CVSS Calculator', description: 'Severity scoring' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '📊', label: 'Executive Summary', prompt: 'Generate an executive summary of our findings' },
|
||||||
|
{ icon: '📋', label: 'Technical Report', prompt: 'Create a detailed technical report' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'retest',
|
||||||
|
name: 'Retesting',
|
||||||
|
short: 'Retest',
|
||||||
|
icon: '🔄',
|
||||||
|
description: 'Verify that vulnerabilities have been properly remediated.',
|
||||||
|
tools: [
|
||||||
|
{ name: 'Verification Scan', description: 'Re-run previous scans' },
|
||||||
|
],
|
||||||
|
quickActions: [
|
||||||
|
{ icon: '✅', label: 'Verify Fixes', prompt: 'Create a retest plan for the identified vulnerabilities' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.checkStatus();
|
||||||
|
await this.checkRunningProcesses();
|
||||||
|
setInterval(() => this.checkStatus(), 30000);
|
||||||
|
setInterval(() => this.checkRunningProcesses(), 5000);
|
||||||
|
await this.loadProviders();
|
||||||
|
await this.refreshScans();
|
||||||
|
setInterval(() => this.pollRunningScans(), 5000);
|
||||||
|
|
||||||
|
this.messages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
phase: 'Reconnaissance',
|
||||||
|
content: `# Welcome to StrikePackageGPT! 🍁
|
||||||
|
|
||||||
|
I'm your AI-powered penetration testing assistant, following the **6-Phase Enterprise Methodology**:
|
||||||
|
|
||||||
|
| Phase | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| 🔍 **Reconnaissance** | OSINT, subdomain discovery, port scanning |
|
||||||
|
| 📡 **Scanning** | Service enumeration, version detection |
|
||||||
|
| 🛡️ **Vulnerability Assessment** | CVE identification, CVSS scoring |
|
||||||
|
| 💥 **Exploitation** | Safe, controlled exploitation |
|
||||||
|
| 📄 **Reporting** | Executive & technical reports |
|
||||||
|
| 🔄 **Retesting** | Verify remediation |
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- ⛓️ **Attack Chain Analysis** - Correlate vulnerabilities into attack paths
|
||||||
|
- 🎯 **CVSS Scoring** - Risk severity badges on all findings
|
||||||
|
- 🤖 **Context-Aware AI** - Prompts tailored to each phase
|
||||||
|
|
||||||
|
Select a phase above to begin, or use the quick actions in the sidebar!`
|
||||||
|
});
|
||||||
|
|
||||||
|
this.terminalHistory.push({
|
||||||
|
type: 'info',
|
||||||
|
content: 'Connected to Kali container. Type commands below to execute.'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentPhase() {
|
||||||
|
return this.phases.find(p => p.id === this.activePhase) || this.phases[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
getPhasePrompt() {
|
||||||
|
const prompts = {
|
||||||
|
recon: 'Ask about reconnaissance techniques or specify a target to scan...',
|
||||||
|
scanning: 'Request service enumeration or detailed scanning...',
|
||||||
|
vuln: 'Ask about vulnerability assessment or analyze scan results...',
|
||||||
|
exploit: 'Discuss exploitation strategies or search for exploits...',
|
||||||
|
report: 'Request report generation or findings summary...',
|
||||||
|
retest: 'Plan retesting or verify remediation...'
|
||||||
|
};
|
||||||
|
return prompts[this.activePhase] || 'Ask me anything about security testing...';
|
||||||
|
},
|
||||||
|
|
||||||
|
get phaseScans() {
|
||||||
|
const phaseTools = {
|
||||||
|
recon: ['nmap', 'theHarvester', 'amass', 'whatweb', 'whois'],
|
||||||
|
scanning: ['nmap', 'masscan', 'enum4linux', 'gobuster'],
|
||||||
|
vuln: ['nikto', 'nuclei', 'sqlmap', 'searchsploit'],
|
||||||
|
exploit: ['hydra', 'metasploit'],
|
||||||
|
report: [],
|
||||||
|
retest: []
|
||||||
|
};
|
||||||
|
const tools = phaseTools[this.activePhase] || [];
|
||||||
|
return this.scans.filter(s => tools.includes(s.tool));
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/status');
|
||||||
|
const data = await response.json();
|
||||||
|
this.services = data.services;
|
||||||
|
} catch (e) { console.error('Failed to check status:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkRunningProcesses() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/processes');
|
||||||
|
const data = await response.json();
|
||||||
|
this.runningProcesses = data.running_processes || [];
|
||||||
|
} catch (e) { console.error('Failed to check processes:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadProviders() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/providers');
|
||||||
|
this.providers = await response.json();
|
||||||
|
this.updateModels();
|
||||||
|
} catch (e) { console.error('Failed to load providers:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
updateModels() {
|
||||||
|
if (this.providers[this.selectedProvider]) {
|
||||||
|
this.availableModels = this.providers[this.selectedProvider].models || [];
|
||||||
|
this.selectedModel = this.availableModels[0] || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendMessage() {
|
||||||
|
if (!this.userInput.trim() || this.isLoading) return;
|
||||||
|
const message = this.userInput;
|
||||||
|
this.userInput = '';
|
||||||
|
this.messages.push({ role: 'user', content: message });
|
||||||
|
this.isLoading = true;
|
||||||
|
this.scrollToBottom('chatContainer');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/chat/phase', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
phase: this.activePhase,
|
||||||
|
provider: this.selectedProvider,
|
||||||
|
model: this.selectedModel,
|
||||||
|
findings: this.findings
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const aiMessage = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: data.content || 'Sorry, I encountered an error.',
|
||||||
|
phase: this.getCurrentPhase().name,
|
||||||
|
risk_score: data.risk_score,
|
||||||
|
findings: data.findings
|
||||||
|
};
|
||||||
|
this.messages.push(aiMessage);
|
||||||
|
if (data.findings && data.findings.length > 0) {
|
||||||
|
this.findings = [...this.findings, ...data.findings];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.messages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: '❌ Failed to connect to the backend service.',
|
||||||
|
phase: this.getCurrentPhase().name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
this.scrollToBottom('chatContainer');
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeCommand() {
|
||||||
|
if (!this.terminalInput.trim() || this.terminalLoading) return;
|
||||||
|
const command = this.terminalInput;
|
||||||
|
this.commandHistory.unshift(command);
|
||||||
|
this.historyIndex = -1;
|
||||||
|
this.terminalInput = '';
|
||||||
|
this.terminalHistory.push({ type: 'cmd', content: command });
|
||||||
|
this.terminalLoading = true;
|
||||||
|
this.scrollToBottom('terminalOutput');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/execute', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ command: command, timeout: 300 })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.stdout) this.terminalHistory.push({ type: 'stdout', content: data.stdout });
|
||||||
|
if (data.stderr) this.terminalHistory.push({ type: 'stderr', content: data.stderr });
|
||||||
|
if (data.exit_code !== 0) this.terminalHistory.push({ type: 'info', content: `Exit code: ${data.exit_code}` });
|
||||||
|
} catch (e) {
|
||||||
|
this.terminalHistory.push({ type: 'stderr', content: 'Failed to execute command: ' + e.message });
|
||||||
|
}
|
||||||
|
this.terminalLoading = false;
|
||||||
|
this.scrollToBottom('terminalOutput');
|
||||||
|
},
|
||||||
|
|
||||||
|
historyUp() {
|
||||||
|
if (this.historyIndex < this.commandHistory.length - 1) {
|
||||||
|
this.historyIndex++;
|
||||||
|
this.terminalInput = this.commandHistory[this.historyIndex];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
historyDown() {
|
||||||
|
if (this.historyIndex > 0) {
|
||||||
|
this.historyIndex--;
|
||||||
|
this.terminalInput = this.commandHistory[this.historyIndex];
|
||||||
|
} else if (this.historyIndex === 0) {
|
||||||
|
this.historyIndex = -1;
|
||||||
|
this.terminalInput = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
executeQuickAction(action) {
|
||||||
|
if (action.tool) {
|
||||||
|
this.showScanModal(action.tool, action.scanType);
|
||||||
|
} else if (action.prompt) {
|
||||||
|
this.userInput = action.prompt;
|
||||||
|
this.activeTab = 'phase';
|
||||||
|
this.sendMessage();
|
||||||
|
} else if (action.command) {
|
||||||
|
this.terminalInput = action.command;
|
||||||
|
this.activeTab = 'terminal';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showScanModal(tool, scanType) {
|
||||||
|
const toolConfig = {
|
||||||
|
nmap: ['quick', 'full', 'stealth', 'vuln'],
|
||||||
|
nikto: ['default', 'ssl', 'full'],
|
||||||
|
gobuster: ['dir', 'dns'],
|
||||||
|
sqlmap: ['test', 'dbs'],
|
||||||
|
whatweb: ['default', 'aggressive'],
|
||||||
|
amass: ['default'],
|
||||||
|
hydra: ['default']
|
||||||
|
};
|
||||||
|
this.scanModal = { tool, target: '', scanType, types: toolConfig[tool] || [scanType] };
|
||||||
|
this.scanModalOpen = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async startScan() {
|
||||||
|
if (!this.scanModal.target) return;
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/scan', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
tool: this.scanModal.tool,
|
||||||
|
target: this.scanModal.target,
|
||||||
|
scan_type: this.scanModal.scanType
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
this.scanModalOpen = false;
|
||||||
|
await this.refreshScans();
|
||||||
|
this.messages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
phase: this.getCurrentPhase().name,
|
||||||
|
content: `🔄 Started **${this.scanModal.tool}** scan on \`${this.scanModal.target}\`\n\nScan ID: \`${data.scan_id}\``
|
||||||
|
});
|
||||||
|
} catch (e) { console.error('Failed to start scan:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshScans() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/scans');
|
||||||
|
this.scans = await response.json();
|
||||||
|
this.scans.filter(s => s.status === 'completed' && s.parsed).forEach(scan => {
|
||||||
|
if (scan.parsed.findings) {
|
||||||
|
scan.parsed.findings.forEach(f => {
|
||||||
|
if (!this.findings.find(existing => existing.raw === f.raw)) {
|
||||||
|
this.findings.push({ id: `${scan.scan_id}-${this.findings.length}`, ...f, tool: scan.tool, target: scan.target });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) { console.error('Failed to load scans:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
async pollScan(scanId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/scan/${scanId}`);
|
||||||
|
const scan = await response.json();
|
||||||
|
const index = this.scans.findIndex(s => s.scan_id === scanId);
|
||||||
|
if (index !== -1) this.scans[index] = scan;
|
||||||
|
} catch (e) { console.error('Failed to poll scan:', e); }
|
||||||
|
},
|
||||||
|
|
||||||
|
async pollRunningScans() {
|
||||||
|
for (const scan of this.scans) {
|
||||||
|
if (scan.status === 'running' || scan.status === 'pending') await this.pollScan(scan.scan_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
viewScanDetails(scan) { this.selectedScan = scan; this.detailsModalOpen = true; },
|
||||||
|
|
||||||
|
askAboutTool(tool) {
|
||||||
|
this.activeTab = 'phase';
|
||||||
|
this.userInput = `Explain how to use ${tool.name} for ${this.getCurrentPhase().name.toLowerCase()}. Include common options and example commands.`;
|
||||||
|
this.sendMessage();
|
||||||
|
},
|
||||||
|
|
||||||
|
async analyzeAttackChains() {
|
||||||
|
if (this.findings.length === 0) return;
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/attack-chains', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ findings: this.findings, provider: this.selectedProvider, model: this.selectedModel })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
this.attackChains = data.attack_chains || [];
|
||||||
|
this.activeTab = 'attack-chains';
|
||||||
|
} catch (e) { console.error('Failed to analyze attack chains:', e); }
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
countBySeverity(severity) { return this.findings.filter(f => f.severity === severity).length; },
|
||||||
|
|
||||||
|
getSeverityBadgeClass(severity) {
|
||||||
|
const classes = {
|
||||||
|
critical: 'bg-sev-critical/20 text-sev-critical border border-sev-critical/50',
|
||||||
|
high: 'bg-sev-high/20 text-sev-high border border-sev-high/50',
|
||||||
|
medium: 'bg-sev-medium/20 text-sev-medium border border-sev-medium/50',
|
||||||
|
low: 'bg-sev-low/20 text-sev-low border border-sev-low/50',
|
||||||
|
info: 'bg-sev-info/20 text-sev-info border border-sev-info/50'
|
||||||
|
};
|
||||||
|
return classes[severity] || classes.info;
|
||||||
|
},
|
||||||
|
|
||||||
|
getRiskColor(score) {
|
||||||
|
if (score >= 9) return 'bg-sev-critical/20 text-sev-critical border border-sev-critical/50';
|
||||||
|
if (score >= 7) return 'bg-sev-high/20 text-sev-high border border-sev-high/50';
|
||||||
|
if (score >= 4) return 'bg-sev-medium/20 text-sev-medium border border-sev-medium/50';
|
||||||
|
return 'bg-sev-low/20 text-sev-low border border-sev-low/50';
|
||||||
|
},
|
||||||
|
|
||||||
|
getRiskLabel(score) {
|
||||||
|
if (score >= 9) return 'CRITICAL';
|
||||||
|
if (score >= 7) return 'HIGH';
|
||||||
|
if (score >= 4) return 'MEDIUM';
|
||||||
|
return 'LOW';
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMarkdown(content) { return marked.parse(content || ''); },
|
||||||
|
|
||||||
|
scrollToBottom(containerId) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (container) container.scrollTop = container.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
services/hackgpt-api/Dockerfile
Normal file
16
services/hackgpt-api/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app/ ./app/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8001
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||||
0
services/hackgpt-api/app/__init__.py
Normal file
0
services/hackgpt-api/app/__init__.py
Normal file
1018
services/hackgpt-api/app/main.py
Normal file
1018
services/hackgpt-api/app/main.py
Normal file
File diff suppressed because it is too large
Load Diff
4
services/hackgpt-api/requirements.txt
Normal file
4
services/hackgpt-api/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi==0.115.5
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
httpx==0.28.1
|
||||||
|
pydantic==2.10.2
|
||||||
16
services/kali-executor/Dockerfile
Normal file
16
services/kali-executor/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app/ ./app/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8002
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8002"]
|
||||||
1
services/kali-executor/app/__init__.py
Normal file
1
services/kali-executor/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Kali Executor Service"""
|
||||||
480
services/kali-executor/app/main.py
Normal file
480
services/kali-executor/app/main.py
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
"""
|
||||||
|
Kali Executor Service
|
||||||
|
Executes commands in the Kali container via Docker SDK.
|
||||||
|
"""
|
||||||
|
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
import docker
|
||||||
|
import asyncio
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
# Allowed command prefixes (security whitelist)
|
||||||
|
ALLOWED_COMMANDS = {
|
||||||
|
# Reconnaissance
|
||||||
|
"nmap", "masscan", "amass", "theharvester", "whatweb", "dnsrecon", "fierce",
|
||||||
|
"dig", "nslookup", "host", "whois",
|
||||||
|
# Web testing
|
||||||
|
"nikto", "gobuster", "dirb", "sqlmap", "wpscan", "curl", "wget",
|
||||||
|
# Network utilities
|
||||||
|
"ping", "traceroute", "netcat", "nc", "tcpdump",
|
||||||
|
# Exploitation research
|
||||||
|
"searchsploit", "msfconsole", "msfvenom",
|
||||||
|
# Brute force
|
||||||
|
"hydra", "medusa",
|
||||||
|
# System info
|
||||||
|
"ls", "cat", "head", "tail", "grep", "find", "pwd", "whoami", "id",
|
||||||
|
"uname", "hostname", "ip", "ifconfig", "netstat", "ss",
|
||||||
|
# Python scripts
|
||||||
|
"python", "python3",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Blocked patterns (dangerous commands)
|
||||||
|
BLOCKED_PATTERNS = [
|
||||||
|
r"rm\s+-rf\s+/", # Prevent recursive deletion of root
|
||||||
|
r"mkfs", # Prevent formatting
|
||||||
|
r"dd\s+if=", # Prevent disk operations
|
||||||
|
r">\s*/dev/", # Prevent writing to devices
|
||||||
|
r"chmod\s+777\s+/", # Prevent dangerous permission changes
|
||||||
|
r"shutdown", r"reboot", r"halt", # Prevent system control
|
||||||
|
r"kill\s+-9\s+-1", # Prevent killing all processes
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_command(command: str) -> tuple[bool, str]:
|
||||||
|
"""Validate command against whitelist and blocked patterns."""
|
||||||
|
# Get the base command (first word)
|
||||||
|
parts = command.strip().split()
|
||||||
|
if not parts:
|
||||||
|
return False, "Empty command"
|
||||||
|
|
||||||
|
base_cmd = parts[0].split("/")[-1] # Handle full paths
|
||||||
|
|
||||||
|
# Check blocked patterns first
|
||||||
|
for pattern in BLOCKED_PATTERNS:
|
||||||
|
if re.search(pattern, command, re.IGNORECASE):
|
||||||
|
return False, f"Blocked pattern detected: {pattern}"
|
||||||
|
|
||||||
|
# Check if command is in whitelist
|
||||||
|
if base_cmd not in ALLOWED_COMMANDS:
|
||||||
|
return False, f"Command '{base_cmd}' not in allowed list"
|
||||||
|
|
||||||
|
return True, "OK"
|
||||||
|
|
||||||
|
|
||||||
|
# Docker client
|
||||||
|
docker_client = None
|
||||||
|
kali_container = None
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""Lifecycle manager for the FastAPI app."""
|
||||||
|
global docker_client, kali_container
|
||||||
|
|
||||||
|
try:
|
||||||
|
docker_client = docker.from_env()
|
||||||
|
kali_container = docker_client.containers.get(
|
||||||
|
os.getenv("KALI_CONTAINER_NAME", "strikepackage-kali")
|
||||||
|
)
|
||||||
|
print(f"Connected to Kali container: {kali_container.name}")
|
||||||
|
except docker.errors.NotFound:
|
||||||
|
print("Warning: Kali container not found. Command execution will fail.")
|
||||||
|
except docker.errors.DockerException as e:
|
||||||
|
print(f"Warning: Docker not available: {e}")
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
if docker_client:
|
||||||
|
docker_client.close()
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Kali Executor",
|
||||||
|
description="Execute commands in the Kali container",
|
||||||
|
version="0.1.0",
|
||||||
|
lifespan=lifespan
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store running commands
|
||||||
|
running_commands: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
class CommandRequest(BaseModel):
|
||||||
|
command: str
|
||||||
|
timeout: int = Field(default=300, ge=1, le=3600)
|
||||||
|
working_dir: str = "/workspace"
|
||||||
|
stream: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class CommandResult(BaseModel):
|
||||||
|
command_id: str
|
||||||
|
command: str
|
||||||
|
status: str
|
||||||
|
exit_code: Optional[int] = None
|
||||||
|
stdout: str = ""
|
||||||
|
stderr: str = ""
|
||||||
|
started_at: datetime
|
||||||
|
completed_at: Optional[datetime] = None
|
||||||
|
duration_seconds: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint."""
|
||||||
|
kali_status = "disconnected"
|
||||||
|
|
||||||
|
if kali_container:
|
||||||
|
try:
|
||||||
|
kali_container.reload()
|
||||||
|
kali_status = kali_container.status
|
||||||
|
except:
|
||||||
|
kali_status = "error"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"service": "kali-executor",
|
||||||
|
"kali_container": kali_status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/processes")
|
||||||
|
async def get_running_processes():
|
||||||
|
"""Get list of running security tool processes in Kali container."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container not available")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get process list
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
exit_code, output = await loop.run_in_executor(
|
||||||
|
executor,
|
||||||
|
lambda: kali_container.exec_run(
|
||||||
|
cmd=["ps", "aux", "--sort=-start_time"],
|
||||||
|
demux=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout = output[0].decode('utf-8', errors='replace') if output[0] else ""
|
||||||
|
|
||||||
|
# Parse processes and filter for security tools
|
||||||
|
security_tools = ["nmap", "nikto", "gobuster", "sqlmap", "hydra", "masscan",
|
||||||
|
"amass", "theharvester", "dirb", "wpscan", "searchsploit", "msfconsole"]
|
||||||
|
|
||||||
|
processes = []
|
||||||
|
for line in stdout.split('\n')[1:]: # Skip header
|
||||||
|
parts = line.split(None, 10)
|
||||||
|
if len(parts) >= 11:
|
||||||
|
cmd = parts[10]
|
||||||
|
pid = parts[1]
|
||||||
|
cpu = parts[2]
|
||||||
|
mem = parts[3]
|
||||||
|
time_running = parts[9]
|
||||||
|
|
||||||
|
# Check if it's a security tool
|
||||||
|
is_security_tool = any(tool in cmd.lower() for tool in security_tools)
|
||||||
|
|
||||||
|
if is_security_tool:
|
||||||
|
processes.append({
|
||||||
|
"pid": pid,
|
||||||
|
"cpu": cpu,
|
||||||
|
"mem": mem,
|
||||||
|
"time": time_running,
|
||||||
|
"command": cmd[:200] # Truncate long commands
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"running_processes": processes,
|
||||||
|
"count": len(processes)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# Thread pool for blocking Docker operations
|
||||||
|
executor = ThreadPoolExecutor(max_workers=10)
|
||||||
|
|
||||||
|
def _run_command_sync(container, command, working_dir):
|
||||||
|
"""Synchronous command execution for thread pool."""
|
||||||
|
full_command = f"cd {working_dir} && {command}"
|
||||||
|
return container.exec_run(
|
||||||
|
cmd=["bash", "-c", full_command],
|
||||||
|
demux=True,
|
||||||
|
workdir=working_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.post("/execute", response_model=CommandResult)
|
||||||
|
async def execute_command(request: CommandRequest):
|
||||||
|
"""Execute a command in the Kali container."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container not available")
|
||||||
|
|
||||||
|
# Validate command against whitelist
|
||||||
|
is_valid, message = validate_command(request.command)
|
||||||
|
if not is_valid:
|
||||||
|
raise HTTPException(status_code=403, detail=f"Command blocked: {message}")
|
||||||
|
|
||||||
|
command_id = str(uuid.uuid4())
|
||||||
|
started_at = datetime.utcnow()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Refresh container state
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
await loop.run_in_executor(executor, kali_container.reload)
|
||||||
|
|
||||||
|
if kali_container.status != "running":
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container is not running")
|
||||||
|
|
||||||
|
# Execute command in thread pool to avoid blocking
|
||||||
|
exit_code, output = await loop.run_in_executor(
|
||||||
|
executor,
|
||||||
|
_run_command_sync,
|
||||||
|
kali_container,
|
||||||
|
request.command,
|
||||||
|
request.working_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
completed_at = datetime.utcnow()
|
||||||
|
duration = (completed_at - started_at).total_seconds()
|
||||||
|
|
||||||
|
stdout = output[0].decode('utf-8', errors='replace') if output[0] else ""
|
||||||
|
stderr = output[1].decode('utf-8', errors='replace') if output[1] else ""
|
||||||
|
|
||||||
|
return CommandResult(
|
||||||
|
command_id=command_id,
|
||||||
|
command=request.command,
|
||||||
|
status="completed",
|
||||||
|
exit_code=exit_code,
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr,
|
||||||
|
started_at=started_at,
|
||||||
|
completed_at=completed_at,
|
||||||
|
duration_seconds=duration
|
||||||
|
)
|
||||||
|
|
||||||
|
except docker.errors.APIError as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Docker error: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Execution error: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/execute/async")
|
||||||
|
async def execute_command_async(request: CommandRequest):
|
||||||
|
"""Execute a command asynchronously and return immediately."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container not available")
|
||||||
|
|
||||||
|
# Validate command against whitelist
|
||||||
|
is_valid, message = validate_command(request.command)
|
||||||
|
if not is_valid:
|
||||||
|
raise HTTPException(status_code=403, detail=f"Command blocked: {message}")
|
||||||
|
|
||||||
|
command_id = str(uuid.uuid4())
|
||||||
|
started_at = datetime.utcnow()
|
||||||
|
|
||||||
|
running_commands[command_id] = {
|
||||||
|
"command": request.command,
|
||||||
|
"status": "running",
|
||||||
|
"started_at": started_at,
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start background execution
|
||||||
|
asyncio.create_task(_run_command_background(
|
||||||
|
command_id, request.command, request.working_dir, request.timeout
|
||||||
|
))
|
||||||
|
|
||||||
|
return {"command_id": command_id, "status": "running"}
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_command_background(command_id: str, command: str, working_dir: str, timeout: int):
|
||||||
|
"""Run command in background."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
try:
|
||||||
|
kali_container.reload()
|
||||||
|
|
||||||
|
full_command = f"cd {working_dir} && timeout {timeout} {command}"
|
||||||
|
|
||||||
|
exit_code, output = kali_container.exec_run(
|
||||||
|
cmd=["bash", "-c", full_command],
|
||||||
|
demux=True,
|
||||||
|
workdir=working_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
running_commands[command_id].update({
|
||||||
|
"status": "completed",
|
||||||
|
"exit_code": exit_code,
|
||||||
|
"stdout": output[0].decode('utf-8', errors='replace') if output[0] else "",
|
||||||
|
"stderr": output[1].decode('utf-8', errors='replace') if output[1] else "",
|
||||||
|
"completed_at": datetime.utcnow()
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
running_commands[command_id].update({
|
||||||
|
"status": "failed",
|
||||||
|
"error": str(e),
|
||||||
|
"completed_at": datetime.utcnow()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/execute/{command_id}")
|
||||||
|
async def get_command_status(command_id: str):
|
||||||
|
"""Get status of an async command."""
|
||||||
|
if command_id not in running_commands:
|
||||||
|
raise HTTPException(status_code=404, detail="Command not found")
|
||||||
|
|
||||||
|
return running_commands[command_id]
|
||||||
|
|
||||||
|
|
||||||
|
@app.websocket("/ws/execute")
|
||||||
|
async def websocket_execute(websocket: WebSocket):
|
||||||
|
"""WebSocket endpoint for streaming command output."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
await websocket.accept()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = await websocket.receive_json()
|
||||||
|
command = data.get("command")
|
||||||
|
working_dir = data.get("working_dir", "/workspace")
|
||||||
|
|
||||||
|
if not command:
|
||||||
|
await websocket.send_json({"error": "No command provided"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Validate command against whitelist
|
||||||
|
is_valid, message = validate_command(command)
|
||||||
|
if not is_valid:
|
||||||
|
await websocket.send_json({"error": f"Command blocked: {message}"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
await websocket.send_json({"error": "Kali container not available"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
kali_container.reload()
|
||||||
|
|
||||||
|
# Use exec_run with stream=True for real-time output
|
||||||
|
exec_result = kali_container.exec_run(
|
||||||
|
cmd=["bash", "-c", f"cd {working_dir} && {command}"],
|
||||||
|
stream=True,
|
||||||
|
demux=True,
|
||||||
|
workdir=working_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stream output
|
||||||
|
for stdout, stderr in exec_result.output:
|
||||||
|
if stdout:
|
||||||
|
await websocket.send_json({
|
||||||
|
"type": "stdout",
|
||||||
|
"data": stdout.decode('utf-8', errors='replace')
|
||||||
|
})
|
||||||
|
if stderr:
|
||||||
|
await websocket.send_json({
|
||||||
|
"type": "stderr",
|
||||||
|
"data": stderr.decode('utf-8', errors='replace')
|
||||||
|
})
|
||||||
|
|
||||||
|
await websocket.send_json({
|
||||||
|
"type": "complete",
|
||||||
|
"exit_code": exec_result.exit_code if hasattr(exec_result, 'exit_code') else 0
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await websocket.send_json({"type": "error", "message": str(e)})
|
||||||
|
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/container/info")
|
||||||
|
async def get_container_info():
|
||||||
|
"""Get Kali container information."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container not available")
|
||||||
|
|
||||||
|
try:
|
||||||
|
kali_container.reload()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": kali_container.short_id,
|
||||||
|
"name": kali_container.name,
|
||||||
|
"status": kali_container.status,
|
||||||
|
"image": kali_container.image.tags[0] if kali_container.image.tags else "unknown",
|
||||||
|
"created": kali_container.attrs.get("Created"),
|
||||||
|
"network": list(kali_container.attrs.get("NetworkSettings", {}).get("Networks", {}).keys())
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/tools")
|
||||||
|
async def list_installed_tools():
|
||||||
|
"""List security tools installed in Kali container."""
|
||||||
|
global kali_container
|
||||||
|
|
||||||
|
if not kali_container:
|
||||||
|
raise HTTPException(status_code=503, detail="Kali container not available")
|
||||||
|
|
||||||
|
tools_to_check = [
|
||||||
|
"nmap", "masscan", "nikto", "sqlmap", "gobuster", "dirb",
|
||||||
|
"hydra", "amass", "theharvester", "whatweb", "wpscan",
|
||||||
|
"searchsploit", "msfconsole", "netcat", "curl", "wget"
|
||||||
|
]
|
||||||
|
|
||||||
|
installed = []
|
||||||
|
|
||||||
|
for tool in tools_to_check:
|
||||||
|
try:
|
||||||
|
exit_code, _ = kali_container.exec_run(
|
||||||
|
cmd=["which", tool],
|
||||||
|
demux=True
|
||||||
|
)
|
||||||
|
if exit_code == 0:
|
||||||
|
installed.append(tool)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {"installed_tools": installed}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/allowed-commands")
|
||||||
|
async def get_allowed_commands():
|
||||||
|
"""Get list of allowed commands for security validation."""
|
||||||
|
return {
|
||||||
|
"allowed_commands": sorted(list(ALLOWED_COMMANDS)),
|
||||||
|
"blocked_patterns": BLOCKED_PATTERNS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8002)
|
||||||
5
services/kali-executor/requirements.txt
Normal file
5
services/kali-executor/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi==0.115.5
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
docker==7.1.0
|
||||||
|
pydantic==2.10.2
|
||||||
|
websockets==14.1
|
||||||
58
services/kali/Dockerfile
Normal file
58
services/kali/Dockerfile
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
FROM kalilinux/kali-rolling
|
||||||
|
|
||||||
|
# Avoid prompts during package installation
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Update and install essential security tools
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
# Core utilities
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
git \
|
||||||
|
vim \
|
||||||
|
net-tools \
|
||||||
|
iputils-ping \
|
||||||
|
dnsutils \
|
||||||
|
# Reconnaissance tools
|
||||||
|
nmap \
|
||||||
|
masscan \
|
||||||
|
amass \
|
||||||
|
theharvester \
|
||||||
|
whatweb \
|
||||||
|
dnsrecon \
|
||||||
|
fierce \
|
||||||
|
# Web testing tools
|
||||||
|
nikto \
|
||||||
|
gobuster \
|
||||||
|
dirb \
|
||||||
|
sqlmap \
|
||||||
|
# Network tools
|
||||||
|
netcat-openbsd \
|
||||||
|
tcpdump \
|
||||||
|
wireshark-common \
|
||||||
|
hydra \
|
||||||
|
# Exploitation
|
||||||
|
metasploit-framework \
|
||||||
|
exploitdb \
|
||||||
|
# Scripting
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install additional Python tools
|
||||||
|
RUN pip3 install --break-system-packages \
|
||||||
|
requests \
|
||||||
|
beautifulsoup4 \
|
||||||
|
shodan \
|
||||||
|
censys
|
||||||
|
|
||||||
|
# Create workspace directory
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
21
services/kali/entrypoint.sh
Normal file
21
services/kali/entrypoint.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "=================================================="
|
||||||
|
echo " StrikePackageGPT - Kali Container"
|
||||||
|
echo " Security Tools Ready"
|
||||||
|
echo "=================================================="
|
||||||
|
echo ""
|
||||||
|
echo "Available tools:"
|
||||||
|
echo " - nmap, masscan (port scanning)"
|
||||||
|
echo " - amass, theharvester (reconnaissance)"
|
||||||
|
echo " - nikto, gobuster (web testing)"
|
||||||
|
echo " - sqlmap (SQL injection)"
|
||||||
|
echo " - hydra (brute force)"
|
||||||
|
echo " - metasploit (exploitation)"
|
||||||
|
echo " - searchsploit (exploit database)"
|
||||||
|
echo ""
|
||||||
|
echo "Container is ready for security testing."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Keep container running
|
||||||
|
exec sleep infinity
|
||||||
16
services/llm-router/Dockerfile
Normal file
16
services/llm-router/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app/ ./app/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
0
services/llm-router/app/__init__.py
Normal file
0
services/llm-router/app/__init__.py
Normal file
213
services/llm-router/app/main.py
Normal file
213
services/llm-router/app/main.py
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
"""
|
||||||
|
LLM Router Service
|
||||||
|
Routes requests to different LLM providers (OpenAI, Anthropic, Ollama)
|
||||||
|
"""
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional, Literal
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="LLM Router",
|
||||||
|
description="Routes requests to multiple LLM providers",
|
||||||
|
version="0.1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configuration from environment
|
||||||
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
||||||
|
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
|
||||||
|
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://192.168.1.50:11434")
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMessage(BaseModel):
|
||||||
|
role: Literal["system", "user", "assistant"]
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRequest(BaseModel):
|
||||||
|
provider: Literal["openai", "anthropic", "ollama"] = "ollama"
|
||||||
|
model: str = "llama3.2"
|
||||||
|
messages: list[ChatMessage]
|
||||||
|
temperature: float = 0.7
|
||||||
|
max_tokens: int = 2048
|
||||||
|
|
||||||
|
|
||||||
|
class ChatResponse(BaseModel):
|
||||||
|
provider: str
|
||||||
|
model: str
|
||||||
|
content: str
|
||||||
|
usage: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {"status": "healthy", "service": "llm-router"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/providers")
|
||||||
|
async def list_providers():
|
||||||
|
"""List available LLM providers and their status"""
|
||||||
|
# Dynamically fetch Ollama models
|
||||||
|
ollama_models = []
|
||||||
|
ollama_available = False
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=5.0)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
ollama_models = [m["name"] for m in data.get("models", [])]
|
||||||
|
ollama_available = True
|
||||||
|
except Exception:
|
||||||
|
ollama_models = ["llama3", "mistral", "codellama"] # fallback
|
||||||
|
|
||||||
|
providers = {
|
||||||
|
"openai": {"available": bool(OPENAI_API_KEY), "models": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"]},
|
||||||
|
"anthropic": {"available": bool(ANTHROPIC_API_KEY), "models": ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"]},
|
||||||
|
"ollama": {"available": ollama_available, "base_url": OLLAMA_BASE_URL, "models": ollama_models}
|
||||||
|
}
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/chat", response_model=ChatResponse)
|
||||||
|
async def chat(request: ChatRequest):
|
||||||
|
"""Route chat request to specified LLM provider"""
|
||||||
|
|
||||||
|
if request.provider == "openai":
|
||||||
|
return await _call_openai(request)
|
||||||
|
elif request.provider == "anthropic":
|
||||||
|
return await _call_anthropic(request)
|
||||||
|
elif request.provider == "ollama":
|
||||||
|
return await _call_ollama(request)
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Unknown provider: {request.provider}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _call_openai(request: ChatRequest) -> ChatResponse:
|
||||||
|
"""Call OpenAI API"""
|
||||||
|
if not OPENAI_API_KEY:
|
||||||
|
raise HTTPException(status_code=503, detail="OpenAI API key not configured")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
"https://api.openai.com/v1/chat/completions",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {OPENAI_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
"model": request.model,
|
||||||
|
"messages": [m.model_dump() for m in request.messages],
|
||||||
|
"temperature": request.temperature,
|
||||||
|
"max_tokens": request.max_tokens
|
||||||
|
},
|
||||||
|
timeout=60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return ChatResponse(
|
||||||
|
provider="openai",
|
||||||
|
model=request.model,
|
||||||
|
content=data["choices"][0]["message"]["content"],
|
||||||
|
usage=data.get("usage")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _call_anthropic(request: ChatRequest) -> ChatResponse:
|
||||||
|
"""Call Anthropic API"""
|
||||||
|
if not ANTHROPIC_API_KEY:
|
||||||
|
raise HTTPException(status_code=503, detail="Anthropic API key not configured")
|
||||||
|
|
||||||
|
# Extract system message if present
|
||||||
|
system_msg = ""
|
||||||
|
messages = []
|
||||||
|
for msg in request.messages:
|
||||||
|
if msg.role == "system":
|
||||||
|
system_msg = msg.content
|
||||||
|
else:
|
||||||
|
messages.append({"role": msg.role, "content": msg.content})
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
payload = {
|
||||||
|
"model": request.model,
|
||||||
|
"messages": messages,
|
||||||
|
"max_tokens": request.max_tokens,
|
||||||
|
"temperature": request.temperature
|
||||||
|
}
|
||||||
|
if system_msg:
|
||||||
|
payload["system"] = system_msg
|
||||||
|
|
||||||
|
response = await client.post(
|
||||||
|
"https://api.anthropic.com/v1/messages",
|
||||||
|
headers={
|
||||||
|
"x-api-key": ANTHROPIC_API_KEY,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"anthropic-version": "2023-06-01"
|
||||||
|
},
|
||||||
|
json=payload,
|
||||||
|
timeout=60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return ChatResponse(
|
||||||
|
provider="anthropic",
|
||||||
|
model=request.model,
|
||||||
|
content=data["content"][0]["text"],
|
||||||
|
usage=data.get("usage")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _call_ollama(request: ChatRequest) -> ChatResponse:
|
||||||
|
"""Call Ollama API (local models)"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
response = await client.post(
|
||||||
|
f"{OLLAMA_BASE_URL}/api/chat",
|
||||||
|
json={
|
||||||
|
"model": request.model,
|
||||||
|
"messages": [m.model_dump() for m in request.messages],
|
||||||
|
"stream": False,
|
||||||
|
"options": {
|
||||||
|
"temperature": request.temperature,
|
||||||
|
"num_predict": request.max_tokens
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeout=120.0
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=response.text)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return ChatResponse(
|
||||||
|
provider="ollama",
|
||||||
|
model=request.model,
|
||||||
|
content=data["message"]["content"],
|
||||||
|
usage={
|
||||||
|
"prompt_tokens": data.get("prompt_eval_count", 0),
|
||||||
|
"completion_tokens": data.get("eval_count", 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except httpx.ConnectError:
|
||||||
|
raise HTTPException(status_code=503, detail="Ollama service not available")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
4
services/llm-router/requirements.txt
Normal file
4
services/llm-router/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi==0.115.5
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
httpx==0.28.1
|
||||||
|
pydantic==2.10.2
|
||||||
4
services/shared/__init__.py
Normal file
4
services/shared/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"""
|
||||||
|
StrikePackageGPT Shared Library
|
||||||
|
Common models, utilities, and constants used across services.
|
||||||
|
"""
|
||||||
158
services/shared/models.py
Normal file
158
services/shared/models.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
Shared Pydantic models for StrikePackageGPT services.
|
||||||
|
"""
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Literal, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class TaskState(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
RUNNING = "running"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
FAILED = "failed"
|
||||||
|
CANCELLED = "cancelled"
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCategory(str, Enum):
|
||||||
|
RECON = "reconnaissance"
|
||||||
|
VULN_SCAN = "vulnerability_scanning"
|
||||||
|
EXPLOITATION = "exploitation"
|
||||||
|
WEB_TESTING = "web_testing"
|
||||||
|
PASSWORD = "password_attacks"
|
||||||
|
WIRELESS = "wireless"
|
||||||
|
FORENSICS = "forensics"
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Chat Models ==============
|
||||||
|
|
||||||
|
class ChatMessage(BaseModel):
|
||||||
|
role: Literal["system", "user", "assistant"]
|
||||||
|
content: str
|
||||||
|
timestamp: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRequest(BaseModel):
|
||||||
|
message: str
|
||||||
|
session_id: Optional[str] = None
|
||||||
|
context: Optional[str] = None
|
||||||
|
provider: str = "ollama"
|
||||||
|
model: str = "llama3.2"
|
||||||
|
temperature: float = 0.7
|
||||||
|
max_tokens: int = 2048
|
||||||
|
|
||||||
|
|
||||||
|
class ChatResponse(BaseModel):
|
||||||
|
provider: str
|
||||||
|
model: str
|
||||||
|
content: str
|
||||||
|
usage: Optional[Dict[str, int]] = None
|
||||||
|
session_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Command Execution Models ==============
|
||||||
|
|
||||||
|
class CommandRequest(BaseModel):
|
||||||
|
command: str
|
||||||
|
timeout: int = Field(default=300, ge=1, le=3600)
|
||||||
|
working_dir: Optional[str] = "/workspace"
|
||||||
|
env: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CommandResult(BaseModel):
|
||||||
|
command: str
|
||||||
|
exit_code: int
|
||||||
|
stdout: str
|
||||||
|
stderr: str
|
||||||
|
duration_seconds: float
|
||||||
|
timed_out: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Task Models ==============
|
||||||
|
|
||||||
|
class Task(BaseModel):
|
||||||
|
task_id: str
|
||||||
|
task_type: str
|
||||||
|
status: TaskState = TaskState.PENDING
|
||||||
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
started_at: Optional[datetime] = None
|
||||||
|
completed_at: Optional[datetime] = None
|
||||||
|
result: Optional[Any] = None
|
||||||
|
error: Optional[str] = None
|
||||||
|
progress: int = Field(default=0, ge=0, le=100)
|
||||||
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Security Tool Models ==============
|
||||||
|
|
||||||
|
class SecurityTool(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
category: ToolCategory
|
||||||
|
command_template: str
|
||||||
|
required_args: List[str] = []
|
||||||
|
optional_args: List[str] = []
|
||||||
|
output_parser: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ScanTarget(BaseModel):
|
||||||
|
target: str # IP, hostname, URL, or CIDR
|
||||||
|
target_type: Literal["ip", "hostname", "url", "cidr", "auto"] = "auto"
|
||||||
|
ports: Optional[str] = None # e.g., "22,80,443" or "1-1000"
|
||||||
|
options: Dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class ScanRequest(BaseModel):
|
||||||
|
target: ScanTarget
|
||||||
|
tool: str
|
||||||
|
scan_type: Optional[str] = None
|
||||||
|
options: Dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class ScanResult(BaseModel):
|
||||||
|
scan_id: str
|
||||||
|
tool: str
|
||||||
|
target: str
|
||||||
|
status: TaskState
|
||||||
|
started_at: datetime
|
||||||
|
completed_at: Optional[datetime] = None
|
||||||
|
raw_output: Optional[str] = None
|
||||||
|
parsed_results: Optional[Dict[str, Any]] = None
|
||||||
|
findings: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Session Models ==============
|
||||||
|
|
||||||
|
class Session(BaseModel):
|
||||||
|
session_id: str
|
||||||
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
last_activity: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
messages: List[ChatMessage] = []
|
||||||
|
context: Dict[str, Any] = Field(default_factory=dict)
|
||||||
|
active_scans: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
# ============== Finding Models ==============
|
||||||
|
|
||||||
|
class Severity(str, Enum):
|
||||||
|
CRITICAL = "critical"
|
||||||
|
HIGH = "high"
|
||||||
|
MEDIUM = "medium"
|
||||||
|
LOW = "low"
|
||||||
|
INFO = "info"
|
||||||
|
|
||||||
|
|
||||||
|
class Finding(BaseModel):
|
||||||
|
finding_id: str
|
||||||
|
title: str
|
||||||
|
description: str
|
||||||
|
severity: Severity
|
||||||
|
category: str
|
||||||
|
target: str
|
||||||
|
evidence: Optional[str] = None
|
||||||
|
remediation: Optional[str] = None
|
||||||
|
references: List[str] = []
|
||||||
|
cve_ids: List[str] = []
|
||||||
|
detected_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
tool: Optional[str] = None
|
||||||
315
services/shared/parsers.py
Normal file
315
services/shared/parsers.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
"""
|
||||||
|
Output parsers for security tool results.
|
||||||
|
Converts raw tool output into structured data.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class BaseParser:
|
||||||
|
"""Base class for tool output parsers."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class NmapParser(BaseParser):
|
||||||
|
"""Parser for nmap output."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
"""Parse nmap text output."""
|
||||||
|
results = {
|
||||||
|
"hosts": [],
|
||||||
|
"scan_info": {},
|
||||||
|
"raw": output
|
||||||
|
}
|
||||||
|
|
||||||
|
current_host = None
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Parse scan info
|
||||||
|
if line.startswith('Nmap scan report for'):
|
||||||
|
if current_host:
|
||||||
|
results["hosts"].append(current_host)
|
||||||
|
|
||||||
|
# Extract hostname and IP
|
||||||
|
match = re.search(r'for (\S+)(?: \((\d+\.\d+\.\d+\.\d+)\))?', line)
|
||||||
|
if match:
|
||||||
|
current_host = {
|
||||||
|
"hostname": match.group(1),
|
||||||
|
"ip": match.group(2) or match.group(1),
|
||||||
|
"ports": [],
|
||||||
|
"os": None,
|
||||||
|
"status": "up"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse port info
|
||||||
|
elif current_host and re.match(r'^\d+/(tcp|udp)', line):
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 3:
|
||||||
|
port_proto = parts[0].split('/')
|
||||||
|
current_host["ports"].append({
|
||||||
|
"port": int(port_proto[0]),
|
||||||
|
"protocol": port_proto[1],
|
||||||
|
"state": parts[1],
|
||||||
|
"service": parts[2] if len(parts) > 2 else "unknown",
|
||||||
|
"version": ' '.join(parts[3:]) if len(parts) > 3 else None
|
||||||
|
})
|
||||||
|
|
||||||
|
# Parse OS detection
|
||||||
|
elif current_host and 'OS details:' in line:
|
||||||
|
current_host["os"] = line.replace('OS details:', '').strip()
|
||||||
|
|
||||||
|
# Parse timing info
|
||||||
|
elif 'scanned in' in line.lower():
|
||||||
|
match = re.search(r'scanned in ([\d.]+) seconds', line)
|
||||||
|
if match:
|
||||||
|
results["scan_info"]["duration_seconds"] = float(match.group(1))
|
||||||
|
|
||||||
|
if current_host:
|
||||||
|
results["hosts"].append(current_host)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def parse_xml(self, xml_output: str) -> Dict[str, Any]:
|
||||||
|
"""Parse nmap XML output for more detailed results."""
|
||||||
|
try:
|
||||||
|
root = ET.fromstring(xml_output)
|
||||||
|
results = {
|
||||||
|
"hosts": [],
|
||||||
|
"scan_info": {
|
||||||
|
"scanner": root.get("scanner"),
|
||||||
|
"args": root.get("args"),
|
||||||
|
"start_time": root.get("start"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for host in root.findall('.//host'):
|
||||||
|
host_info = {
|
||||||
|
"ip": None,
|
||||||
|
"hostname": None,
|
||||||
|
"status": host.find('status').get('state') if host.find('status') is not None else "unknown",
|
||||||
|
"ports": [],
|
||||||
|
"os": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get addresses
|
||||||
|
for addr in host.findall('.//address'):
|
||||||
|
if addr.get('addrtype') == 'ipv4':
|
||||||
|
host_info["ip"] = addr.get('addr')
|
||||||
|
|
||||||
|
# Get hostnames
|
||||||
|
hostname_elem = host.find('.//hostname')
|
||||||
|
if hostname_elem is not None:
|
||||||
|
host_info["hostname"] = hostname_elem.get('name')
|
||||||
|
|
||||||
|
# Get ports
|
||||||
|
for port in host.findall('.//port'):
|
||||||
|
port_info = {
|
||||||
|
"port": int(port.get('portid')),
|
||||||
|
"protocol": port.get('protocol'),
|
||||||
|
"state": port.find('state').get('state') if port.find('state') is not None else "unknown",
|
||||||
|
}
|
||||||
|
|
||||||
|
service = port.find('service')
|
||||||
|
if service is not None:
|
||||||
|
port_info["service"] = service.get('name')
|
||||||
|
port_info["product"] = service.get('product')
|
||||||
|
port_info["version"] = service.get('version')
|
||||||
|
|
||||||
|
host_info["ports"].append(port_info)
|
||||||
|
|
||||||
|
results["hosts"].append(host_info)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except ET.ParseError:
|
||||||
|
return {"error": "Failed to parse XML", "raw": xml_output}
|
||||||
|
|
||||||
|
|
||||||
|
class NiktoParser(BaseParser):
|
||||||
|
"""Parser for nikto output."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
results = {
|
||||||
|
"target": None,
|
||||||
|
"findings": [],
|
||||||
|
"server_info": {},
|
||||||
|
"raw": output
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Target info
|
||||||
|
if '+ Target IP:' in line:
|
||||||
|
results["target"] = line.split(':')[-1].strip()
|
||||||
|
elif '+ Target Hostname:' in line:
|
||||||
|
results["server_info"]["hostname"] = line.split(':')[-1].strip()
|
||||||
|
elif '+ Target Port:' in line:
|
||||||
|
results["server_info"]["port"] = line.split(':')[-1].strip()
|
||||||
|
elif '+ Server:' in line:
|
||||||
|
results["server_info"]["server"] = line.split(':', 1)[-1].strip()
|
||||||
|
|
||||||
|
# Findings (lines starting with +)
|
||||||
|
elif line.startswith('+') and ':' in line:
|
||||||
|
# Skip info lines
|
||||||
|
if any(skip in line for skip in ['Target IP', 'Target Hostname', 'Target Port', 'Server:', 'Start Time', 'End Time']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
finding = {
|
||||||
|
"raw": line[1:].strip(),
|
||||||
|
"severity": "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine severity based on content
|
||||||
|
if any(word in line.lower() for word in ['vulnerable', 'vulnerability', 'exploit']):
|
||||||
|
finding["severity"] = "high"
|
||||||
|
elif any(word in line.lower() for word in ['outdated', 'deprecated', 'insecure']):
|
||||||
|
finding["severity"] = "medium"
|
||||||
|
elif any(word in line.lower() for word in ['disclosed', 'information', 'header']):
|
||||||
|
finding["severity"] = "low"
|
||||||
|
|
||||||
|
# Extract OSVDB if present
|
||||||
|
osvdb_match = re.search(r'OSVDB-(\d+)', line)
|
||||||
|
if osvdb_match:
|
||||||
|
finding["osvdb"] = osvdb_match.group(1)
|
||||||
|
|
||||||
|
results["findings"].append(finding)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class SQLMapParser(BaseParser):
|
||||||
|
"""Parser for sqlmap output."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
results = {
|
||||||
|
"target": None,
|
||||||
|
"parameters": [],
|
||||||
|
"injections": [],
|
||||||
|
"databases": [],
|
||||||
|
"raw": output
|
||||||
|
}
|
||||||
|
|
||||||
|
in_parameter_section = False
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Target URL
|
||||||
|
if 'target URL' in line.lower():
|
||||||
|
match = re.search(r"'([^']+)'", line)
|
||||||
|
if match:
|
||||||
|
results["target"] = match.group(1)
|
||||||
|
|
||||||
|
# Injectable parameters
|
||||||
|
if 'Parameter:' in line:
|
||||||
|
param_match = re.search(r"Parameter: (\S+)", line)
|
||||||
|
if param_match:
|
||||||
|
results["parameters"].append({
|
||||||
|
"name": param_match.group(1),
|
||||||
|
"injectable": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# Injection type
|
||||||
|
if 'Type:' in line and 'injection' in line.lower():
|
||||||
|
results["injections"].append(line.replace('Type:', '').strip())
|
||||||
|
|
||||||
|
# Databases found
|
||||||
|
if line.startswith('[*]') and 'available databases' not in line.lower():
|
||||||
|
db_name = line[3:].strip()
|
||||||
|
if db_name:
|
||||||
|
results["databases"].append(db_name)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class GobusterParser(BaseParser):
|
||||||
|
"""Parser for gobuster output."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
results = {
|
||||||
|
"findings": [],
|
||||||
|
"directories": [],
|
||||||
|
"files": [],
|
||||||
|
"raw": output
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Parse found paths
|
||||||
|
# Format: /path (Status: 200) [Size: 1234]
|
||||||
|
match = re.search(r'^(/\S*)\s+\(Status:\s*(\d+)\)(?:\s+\[Size:\s*(\d+)\])?', line)
|
||||||
|
if match:
|
||||||
|
finding = {
|
||||||
|
"path": match.group(1),
|
||||||
|
"status": int(match.group(2)),
|
||||||
|
"size": int(match.group(3)) if match.group(3) else None
|
||||||
|
}
|
||||||
|
|
||||||
|
results["findings"].append(finding)
|
||||||
|
|
||||||
|
if finding["path"].endswith('/'):
|
||||||
|
results["directories"].append(finding["path"])
|
||||||
|
else:
|
||||||
|
results["files"].append(finding["path"])
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class HydraParser(BaseParser):
|
||||||
|
"""Parser for hydra output."""
|
||||||
|
|
||||||
|
def parse(self, output: str) -> Dict[str, Any]:
|
||||||
|
results = {
|
||||||
|
"credentials": [],
|
||||||
|
"target": None,
|
||||||
|
"service": None,
|
||||||
|
"raw": output
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Parse found credentials
|
||||||
|
# Format: [port][service] host: x login: y password: z
|
||||||
|
cred_match = re.search(r'\[(\d+)\]\[(\w+)\]\s+host:\s+(\S+)\s+login:\s+(\S+)\s+password:\s+(\S+)', line)
|
||||||
|
if cred_match:
|
||||||
|
results["credentials"].append({
|
||||||
|
"port": int(cred_match.group(1)),
|
||||||
|
"service": cred_match.group(2),
|
||||||
|
"host": cred_match.group(3),
|
||||||
|
"username": cred_match.group(4),
|
||||||
|
"password": cred_match.group(5)
|
||||||
|
})
|
||||||
|
results["target"] = cred_match.group(3)
|
||||||
|
results["service"] = cred_match.group(2)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# Registry of parsers
|
||||||
|
PARSERS = {
|
||||||
|
"nmap": NmapParser(),
|
||||||
|
"nikto": NiktoParser(),
|
||||||
|
"sqlmap": SQLMapParser(),
|
||||||
|
"gobuster": GobusterParser(),
|
||||||
|
"hydra": HydraParser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_tool_output(tool: str, output: str) -> Dict[str, Any]:
|
||||||
|
"""Parse output from a security tool."""
|
||||||
|
parser = PARSERS.get(tool.lower())
|
||||||
|
if parser:
|
||||||
|
try:
|
||||||
|
return parser.parse(output)
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e), "raw": output}
|
||||||
|
return {"raw": output}
|
||||||
263
services/shared/tools.py
Normal file
263
services/shared/tools.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
"""
|
||||||
|
Security tool definitions and command builders.
|
||||||
|
"""
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
|
||||||
|
|
||||||
|
SECURITY_TOOLS = {
|
||||||
|
# ============== Reconnaissance ==============
|
||||||
|
"nmap": {
|
||||||
|
"name": "nmap",
|
||||||
|
"description": "Network scanner and security auditing tool",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"quick": "nmap -T4 -F {target}",
|
||||||
|
"full": "nmap -sV -sC -O -p- {target}",
|
||||||
|
"stealth": "nmap -sS -T2 -f {target}",
|
||||||
|
"udp": "nmap -sU --top-ports 100 {target}",
|
||||||
|
"vuln": "nmap --script vuln {target}",
|
||||||
|
"version": "nmap -sV -p {ports} {target}",
|
||||||
|
"os": "nmap -O --osscan-guess {target}",
|
||||||
|
},
|
||||||
|
"default_template": "quick",
|
||||||
|
"output_parser": "nmap"
|
||||||
|
},
|
||||||
|
|
||||||
|
"masscan": {
|
||||||
|
"name": "masscan",
|
||||||
|
"description": "Fast TCP port scanner",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"quick": "masscan {target} --ports 0-1000 --rate 1000",
|
||||||
|
"full": "masscan {target} --ports 0-65535 --rate 10000",
|
||||||
|
"top100": "masscan {target} --top-ports 100 --rate 1000",
|
||||||
|
},
|
||||||
|
"default_template": "quick",
|
||||||
|
},
|
||||||
|
|
||||||
|
"amass": {
|
||||||
|
"name": "amass",
|
||||||
|
"description": "Subdomain enumeration tool",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"passive": "amass enum -passive -d {target}",
|
||||||
|
"active": "amass enum -active -d {target}",
|
||||||
|
"intel": "amass intel -d {target}",
|
||||||
|
},
|
||||||
|
"default_template": "passive",
|
||||||
|
},
|
||||||
|
|
||||||
|
"theharvester": {
|
||||||
|
"name": "theHarvester",
|
||||||
|
"description": "OSINT tool for gathering emails, names, subdomains",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"all": "theHarvester -d {target} -b all",
|
||||||
|
"google": "theHarvester -d {target} -b google",
|
||||||
|
"linkedin": "theHarvester -d {target} -b linkedin",
|
||||||
|
},
|
||||||
|
"default_template": "all",
|
||||||
|
},
|
||||||
|
|
||||||
|
"whatweb": {
|
||||||
|
"name": "whatweb",
|
||||||
|
"description": "Web technology fingerprinting",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"default": "whatweb {target}",
|
||||||
|
"aggressive": "whatweb -a 3 {target}",
|
||||||
|
"verbose": "whatweb -v {target}",
|
||||||
|
},
|
||||||
|
"default_template": "default",
|
||||||
|
},
|
||||||
|
|
||||||
|
"dnsrecon": {
|
||||||
|
"name": "dnsrecon",
|
||||||
|
"description": "DNS enumeration tool",
|
||||||
|
"category": "reconnaissance",
|
||||||
|
"templates": {
|
||||||
|
"standard": "dnsrecon -d {target}",
|
||||||
|
"zone": "dnsrecon -d {target} -t axfr",
|
||||||
|
"brute": "dnsrecon -d {target} -t brt",
|
||||||
|
},
|
||||||
|
"default_template": "standard",
|
||||||
|
},
|
||||||
|
|
||||||
|
# ============== Vulnerability Scanning ==============
|
||||||
|
"nikto": {
|
||||||
|
"name": "nikto",
|
||||||
|
"description": "Web server vulnerability scanner",
|
||||||
|
"category": "vulnerability_scanning",
|
||||||
|
"templates": {
|
||||||
|
"default": "nikto -h {target}",
|
||||||
|
"ssl": "nikto -h {target} -ssl",
|
||||||
|
"tuning": "nikto -h {target} -Tuning x",
|
||||||
|
"full": "nikto -h {target} -C all",
|
||||||
|
},
|
||||||
|
"default_template": "default",
|
||||||
|
"output_parser": "nikto"
|
||||||
|
},
|
||||||
|
|
||||||
|
"sqlmap": {
|
||||||
|
"name": "sqlmap",
|
||||||
|
"description": "SQL injection detection and exploitation",
|
||||||
|
"category": "vulnerability_scanning",
|
||||||
|
"templates": {
|
||||||
|
"test": "sqlmap -u '{target}' --batch",
|
||||||
|
"dbs": "sqlmap -u '{target}' --batch --dbs",
|
||||||
|
"tables": "sqlmap -u '{target}' --batch -D {database} --tables",
|
||||||
|
"dump": "sqlmap -u '{target}' --batch -D {database} -T {table} --dump",
|
||||||
|
"forms": "sqlmap -u '{target}' --batch --forms",
|
||||||
|
},
|
||||||
|
"default_template": "test",
|
||||||
|
"output_parser": "sqlmap"
|
||||||
|
},
|
||||||
|
|
||||||
|
"wpscan": {
|
||||||
|
"name": "wpscan",
|
||||||
|
"description": "WordPress vulnerability scanner",
|
||||||
|
"category": "vulnerability_scanning",
|
||||||
|
"templates": {
|
||||||
|
"default": "wpscan --url {target}",
|
||||||
|
"enumerate": "wpscan --url {target} -e vp,vt,u",
|
||||||
|
"aggressive": "wpscan --url {target} -e ap,at,u --plugins-detection aggressive",
|
||||||
|
},
|
||||||
|
"default_template": "default",
|
||||||
|
},
|
||||||
|
|
||||||
|
# ============== Web Testing ==============
|
||||||
|
"gobuster": {
|
||||||
|
"name": "gobuster",
|
||||||
|
"description": "Directory/file brute-forcing",
|
||||||
|
"category": "web_testing",
|
||||||
|
"templates": {
|
||||||
|
"dir": "gobuster dir -u {target} -w /usr/share/wordlists/dirb/common.txt",
|
||||||
|
"big": "gobuster dir -u {target} -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt",
|
||||||
|
"dns": "gobuster dns -d {target} -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt",
|
||||||
|
"vhost": "gobuster vhost -u {target} -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt",
|
||||||
|
},
|
||||||
|
"default_template": "dir",
|
||||||
|
"output_parser": "gobuster"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ffuf": {
|
||||||
|
"name": "ffuf",
|
||||||
|
"description": "Fast web fuzzer",
|
||||||
|
"category": "web_testing",
|
||||||
|
"templates": {
|
||||||
|
"dir": "ffuf -u {target}/FUZZ -w /usr/share/wordlists/dirb/common.txt",
|
||||||
|
"vhost": "ffuf -u {target} -H 'Host: FUZZ.{domain}' -w /usr/share/wordlists/dns/subdomains-top1million-5000.txt",
|
||||||
|
"param": "ffuf -u '{target}?FUZZ=test' -w /usr/share/wordlists/dirb/common.txt",
|
||||||
|
},
|
||||||
|
"default_template": "dir",
|
||||||
|
},
|
||||||
|
|
||||||
|
"dirb": {
|
||||||
|
"name": "dirb",
|
||||||
|
"description": "Web content scanner",
|
||||||
|
"category": "web_testing",
|
||||||
|
"templates": {
|
||||||
|
"default": "dirb {target}",
|
||||||
|
"small": "dirb {target} /usr/share/wordlists/dirb/small.txt",
|
||||||
|
"big": "dirb {target} /usr/share/wordlists/dirb/big.txt",
|
||||||
|
},
|
||||||
|
"default_template": "default",
|
||||||
|
},
|
||||||
|
|
||||||
|
# ============== Exploitation ==============
|
||||||
|
"searchsploit": {
|
||||||
|
"name": "searchsploit",
|
||||||
|
"description": "Exploit database search tool",
|
||||||
|
"category": "exploitation",
|
||||||
|
"templates": {
|
||||||
|
"search": "searchsploit {query}",
|
||||||
|
"exact": "searchsploit -e {query}",
|
||||||
|
"json": "searchsploit -j {query}",
|
||||||
|
"path": "searchsploit -p {exploit_id}",
|
||||||
|
},
|
||||||
|
"default_template": "search",
|
||||||
|
},
|
||||||
|
|
||||||
|
"hydra": {
|
||||||
|
"name": "hydra",
|
||||||
|
"description": "Network login cracker",
|
||||||
|
"category": "password_attacks",
|
||||||
|
"templates": {
|
||||||
|
"ssh": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} ssh",
|
||||||
|
"ftp": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} ftp",
|
||||||
|
"http_post": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} http-post-form '{form}'",
|
||||||
|
"smb": "hydra -l {user} -P /usr/share/wordlists/rockyou.txt {target} smb",
|
||||||
|
},
|
||||||
|
"default_template": "ssh",
|
||||||
|
"output_parser": "hydra"
|
||||||
|
},
|
||||||
|
|
||||||
|
# ============== Network Tools ==============
|
||||||
|
"netcat": {
|
||||||
|
"name": "nc",
|
||||||
|
"description": "Network utility for TCP/UDP connections",
|
||||||
|
"category": "network",
|
||||||
|
"templates": {
|
||||||
|
"listen": "nc -lvnp {port}",
|
||||||
|
"connect": "nc -v {target} {port}",
|
||||||
|
"scan": "nc -zv {target} {port_range}",
|
||||||
|
},
|
||||||
|
"default_template": "scan",
|
||||||
|
},
|
||||||
|
|
||||||
|
"curl": {
|
||||||
|
"name": "curl",
|
||||||
|
"description": "HTTP client",
|
||||||
|
"category": "web_testing",
|
||||||
|
"templates": {
|
||||||
|
"get": "curl -v {target}",
|
||||||
|
"headers": "curl -I {target}",
|
||||||
|
"post": "curl -X POST -d '{data}' {target}",
|
||||||
|
"follow": "curl -L -v {target}",
|
||||||
|
},
|
||||||
|
"default_template": "get",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_tool(name: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Get tool definition by name."""
|
||||||
|
return SECURITY_TOOLS.get(name.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def get_tools_by_category(category: str) -> List[Dict[str, Any]]:
|
||||||
|
"""Get all tools in a category."""
|
||||||
|
return [tool for tool in SECURITY_TOOLS.values() if tool.get("category") == category]
|
||||||
|
|
||||||
|
|
||||||
|
def build_command(tool_name: str, template_name: str = None, **kwargs) -> Optional[str]:
|
||||||
|
"""Build a command from a tool template."""
|
||||||
|
tool = get_tool(tool_name)
|
||||||
|
if not tool:
|
||||||
|
return None
|
||||||
|
|
||||||
|
template_name = template_name or tool.get("default_template")
|
||||||
|
template = tool.get("templates", {}).get(template_name)
|
||||||
|
|
||||||
|
if not template:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return template.format(**kwargs)
|
||||||
|
except KeyError as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def list_all_tools() -> Dict[str, List[Dict[str, str]]]:
|
||||||
|
"""List all available tools grouped by category."""
|
||||||
|
result = {}
|
||||||
|
for tool in SECURITY_TOOLS.values():
|
||||||
|
category = tool.get("category", "other")
|
||||||
|
if category not in result:
|
||||||
|
result[category] = []
|
||||||
|
result[category].append({
|
||||||
|
"name": tool["name"],
|
||||||
|
"description": tool["description"],
|
||||||
|
"templates": list(tool.get("templates", {}).keys())
|
||||||
|
})
|
||||||
|
return result
|
||||||
Reference in New Issue
Block a user