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