7 Commits

Author SHA1 Message Date
bfb52f098c Add devcontainer compose, dependabot config, and ignore nested repo 2025-12-29 10:20:11 -05:00
e459266e9c Persist dashboard projects data and tighten nmap host filter 2025-12-29 10:16:17 -05:00
af31caeacf Add Vite React component bundling, SSE process streaming, preferences persistence, WebSocket terminal proxy, local Ollama integration
- Enable local Ollama service in compose with llm-router dependency
- Add SSE /stream/processes endpoint in kali-executor for live process updates
- Add WebSocket /ws/execute for real-time terminal command streaming
- Implement preferences persistence (provider/model) via dashboard backend
- Create Vite build pipeline for React components (VoiceControls, NetworkMap, GuidedWizard)
- Update dashboard Dockerfile with Node builder stage for component bundling
- Wire dashboard template to mount components and subscribe to SSE/WebSocket streams
- Add preferences load/save hooks in UI to persist LLM provider/model selection
2025-12-28 21:29:59 -05:00
b971482bbd Dashboard: integrate Cytoscape network map view toggle and mount, add terminal Pause/Scroll Lock/Copy, elapsed time and exit status badges 2025-12-28 21:24:00 -05:00
17f8a332db v3.0: Project management, credentials, notes, exploit suggestions, recon pipelines
Major features:
- Project-based data organization (hosts, scans, credentials, notes saved per project)
- Credential manager with full CRUD operations
- Project notes with categories (recon, exploitation, post-exploit, loot)
- Exploit suggestion engine based on discovered services/versions
- Automated recon pipelines (quick, standard, full, stealth modes)
- searchsploit integration for CVE lookups
- MSF module launcher from host details panel

UI additions:
- Project selector in header with create/details panels
- Credentials tab with table view
- Notes tab with card layout
- Exploit suggestions in network map host details
- Recon Pipeline modal with progress tracking
2025-12-09 23:07:39 -05:00
b1250aa452 v2.3: Full Kali toolkit, improved scanning accuracy
- Install kali-linux-everything metapackage (600+ tools)
- Add --disable-arp-ping to prevent false positives from proxy ARP
- Add MAC address verification for host discovery
- Improve OS detection with scoring system (handles Linux+Samba correctly)
- Fix .21 showing as Windows when it's Linux with xrdp
2025-12-08 13:14:38 -05:00
8b51ba9108 v2.2: Network map improvements and OS filtering
- Fixed jumpy network map: nodes settle in 2 seconds and stay fixed
- Added click vs drag detection for better node interaction
- Made legend clickable as OS type filters (Windows, Linux, macOS, etc.)
- Multiple filters can be active simultaneously (OR logic)
- Added 'Clear filters' button when filters are active
- Added DELETE endpoints to clear network hosts from dashboard
- Fixed nmap parser to only include hosts with open ports
- Nodes stay in place after dragging
2025-12-08 10:17:06 -05:00
22 changed files with 6482 additions and 866 deletions

View File

@@ -0,0 +1,26 @@
version: '3.8'
services:
# Update this to the name of the service you want to work with in your docker-compose.yml file
dashboard:
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
# folder. Note that the path of the Dockerfile and context is relative to the *primary*
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
# array). The sample below assumes your primary file is in the root of your project.
#
# build:
# context: .
# dockerfile: .devcontainer/Dockerfile
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
- ..:/workspaces:cached
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
# cap_add:
# - SYS_PTRACE
# security_opt:
# - seccomp:unconfined
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

3
.gitignore vendored
View File

@@ -32,3 +32,6 @@ data/
# Temporary files
*.tmp
*.temp
# Nested repo
StrikePackageGPT/

View File

@@ -11,6 +11,8 @@ services:
- HACKGPT_API_URL=http://strikepackage-hackgpt-api:8001
- LLM_ROUTER_URL=http://strikepackage-llm-router:8000
- KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002
volumes:
- ./data/dashboard:/app/data
depends_on:
- hackgpt-api
- llm-router
@@ -30,7 +32,7 @@ services:
- LLM_ROUTER_URL=http://strikepackage-llm-router:8000
- KALI_EXECUTOR_URL=http://strikepackage-kali-executor:8002
- DEFAULT_LLM_PROVIDER=${DEFAULT_LLM_PROVIDER:-ollama}
- DEFAULT_LLM_MODEL=${DEFAULT_LLM_MODEL:-llama3.1:latest}
- DEFAULT_LLM_MODEL=${DEFAULT_LLM_MODEL:-llama3.2}
depends_on:
- llm-router
- kali-executor
@@ -50,8 +52,8 @@ services:
- KALI_CONTAINER_NAME=strikepackage-kali
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# depends_on:
# - kali # Temporarily disabled due to Kali mirror SSL issues
depends_on:
- kali
networks:
- strikepackage-net
restart: unless-stopped
@@ -67,19 +69,22 @@ services:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
# Local Ollama endpoint (use host.docker.internal to reach host machine from container)
- OLLAMA_LOCAL_URL=${OLLAMA_LOCAL_URL:-http://host.docker.internal:11434}
# Network Ollama endpoints (comma-separated for multiple)
# Prefer local Ollama container for self-contained setup
- OLLAMA_LOCAL_URL=${OLLAMA_LOCAL_URL:-http://strikepackage-ollama:11434}
# Network Ollama instances (Dell LLM box with larger models)
- OLLAMA_NETWORK_URLS=${OLLAMA_NETWORK_URLS:-http://192.168.1.50:11434}
# Legacy single endpoint (fallback)
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
- OLLAMA_ENDPOINTS=${OLLAMA_ENDPOINTS:-http://strikepackage-ollama:11434}
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://strikepackage-ollama:11434}
# Load balancing: round-robin, random, failover
- LOAD_BALANCE_STRATEGY=${LOAD_BALANCE_STRATEGY:-round-robin}
- LOAD_BALANCE_STRATEGY=${LOAD_BALANCE_STRATEGY:-failover}
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- strikepackage-net
restart: unless-stopped
depends_on:
- ollama
# Kali Linux - Security tools container
kali:
@@ -99,26 +104,25 @@ services:
- 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]
# Ollama - Local LLM
ollama:
image: ollama/ollama:latest
container_name: strikepackage-ollama
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
networks:
- strikepackage-net
restart: unless-stopped
# GPU support (optional): uncomment if using NVIDIA GPU
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
networks:
strikepackage-net:

View File

@@ -1,3 +1,14 @@
FROM node:20-slim AS builder
WORKDIR /build
# Copy package files and JSX components
COPY package.json vite.config.js ./
COPY components/ ./components/
# Install dependencies and build
RUN npm install && npm run build
FROM python:3.12-slim
WORKDIR /app
@@ -11,6 +22,9 @@ COPY app/ ./app/
COPY templates/ ./templates/
COPY static/ ./static/
# Copy built components from builder stage
COPY --from=builder /build/static/dist/ ./static/dist/
# Expose port
EXPOSE 8080

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
import React, { useState } from 'react';
const WIZARD_TYPES = {
first_time_setup: {
title: 'Welcome to GooseStrike',
steps: [
{ id: 'intro', title: 'Introduction', icon: '👋' },
{ id: 'phases', title: 'Methodology', icon: '📋' },
{ id: 'tools', title: 'Security Tools', icon: '🛠️' },
{ id: 'start', title: 'Get Started', icon: '🚀' },
],
},
run_scan: {
title: 'Run Security Scan',
steps: [
{ id: 'target', title: 'Target Selection', icon: '🎯' },
{ id: 'scan-type', title: 'Scan Type', icon: '🔍' },
{ id: 'options', title: 'Options', icon: '⚙️' },
{ id: 'execute', title: 'Execute', icon: '▶️' },
],
},
create_operation: {
title: 'Create Security Operation',
steps: [
{ id: 'details', title: 'Operation Details', icon: '📝' },
{ id: 'scope', title: 'Target Scope', icon: '🎯' },
{ id: 'methodology', title: 'Methodology', icon: '📋' },
{ id: 'review', title: 'Review', icon: '✅' },
],
},
};
const GuidedWizard = ({ type = 'first_time_setup', onComplete = () => {}, onCancel = () => {} }) => {
const wizard = WIZARD_TYPES[type] || WIZARD_TYPES.first_time_setup;
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState({});
const handleNext = () => {
if (currentStep < wizard.steps.length - 1) {
setCurrentStep(currentStep + 1);
} else {
onComplete(formData);
}
};
const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const handleInputChange = (key, value) => {
setFormData({ ...formData, [key]: value });
};
const progress = ((currentStep + 1) / wizard.steps.length) * 100;
return (
<div className="guided-wizard fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
<div className="bg-sp-dark rounded-lg border border-sp-grey-mid w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="p-6 border-b border-sp-grey-mid">
<h2 className="text-2xl font-bold text-sp-white">{wizard.title}</h2>
<div className="mt-4 flex gap-2">
{wizard.steps.map((step, idx) => (
<div
key={step.id}
className={`wizard-step flex-1 p-2 rounded text-center border transition ${
idx === currentStep
? 'border-sp-red bg-sp-red bg-opacity-10'
: idx < currentStep
? 'border-green-500 bg-green-500 bg-opacity-10'
: 'border-sp-grey-mid'
}`}
>
<div className="text-xl">{step.icon}</div>
<div className="text-xs text-sp-white-muted mt-1">{step.title}</div>
</div>
))}
</div>
<div className="mt-3 h-1 bg-sp-grey-mid rounded overflow-hidden">
<div
className="wizard-progress h-full bg-sp-red transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
</div>
{/* Body */}
<div className="flex-1 p-6 overflow-y-auto">
<div className="text-sp-white">
{/* Render step content based on wizard type and current step */}
{type === 'first_time_setup' && currentStep === 0 && (
<div>
<h3 className="text-xl font-bold mb-4">Welcome to GooseStrike! 🍁</h3>
<p className="text-sp-white-muted mb-4">
GooseStrike is an AI-powered penetration testing platform that follows industry-standard
methodologies to help you identify security vulnerabilities.
</p>
<ul className="list-disc list-inside text-sp-white-muted space-y-2">
<li>AI-assisted security analysis with local or cloud LLMs</li>
<li>600+ integrated Kali Linux security tools</li>
<li>Voice control for hands-free operation</li>
<li>Interactive network visualization</li>
<li>Comprehensive reporting and documentation</li>
</ul>
</div>
)}
{type === 'run_scan' && currentStep === 0 && (
<div>
<h3 className="text-xl font-bold mb-4">Select Target</h3>
<label className="block mb-2 text-sm text-sp-white-muted">Target IP or hostname</label>
<input
type="text"
className="w-full bg-sp-grey border border-sp-grey-mid rounded px-3 py-2 text-sp-white"
placeholder="192.168.1.100 or example.com"
value={formData.target || ''}
onChange={(e) => handleInputChange('target', e.target.value)}
/>
</div>
)}
{/* Add more step content as needed */}
</div>
</div>
{/* Footer */}
<div className="p-6 border-t border-sp-grey-mid flex justify-between">
<button
onClick={onCancel}
className="px-4 py-2 bg-sp-grey hover:bg-sp-grey-light rounded text-sp-white transition"
>
Cancel
</button>
<div className="flex gap-2">
{currentStep > 0 && (
<button
onClick={handleBack}
className="px-4 py-2 bg-sp-grey hover:bg-sp-grey-light rounded text-sp-white transition"
>
Back
</button>
)}
<button
onClick={handleNext}
className="px-4 py-2 bg-sp-red hover:bg-sp-red-dark rounded text-sp-white transition"
>
{currentStep === wizard.steps.length - 1 ? 'Complete' : 'Next →'}
</button>
</div>
</div>
</div>
</div>
);
};
export default GuidedWizard;

View File

@@ -0,0 +1,110 @@
import React, { useEffect, useRef } from 'react';
import cytoscape from 'cytoscape';
const NetworkMap = ({ hosts = [], onHostSelect = () => {} }) => {
const containerRef = useRef(null);
const cyRef = useRef(null);
useEffect(() => {
if (!containerRef.current || hosts.length === 0) return;
// Build Cytoscape elements from hosts
const elements = [];
hosts.forEach((host) => {
elements.push({
data: {
id: host.ip,
label: host.hostname || host.ip,
type: host.device_type || 'unknown',
os: host.os || 'unknown',
ports: host.ports || [],
},
classes: host.device_type || 'unknown',
});
// Add edges for network relationships (simple example: connect all to a central gateway)
if (host.ip !== '192.168.1.1') {
elements.push({
data: {
id: `edge-${host.ip}`,
source: '192.168.1.1',
target: host.ip,
},
});
}
});
// Initialize Cytoscape
cyRef.current = cytoscape({
container: containerRef.current,
elements,
style: [
{
selector: 'node',
style: {
'background-color': '#dc2626',
label: 'data(label)',
color: '#e5e5e5',
'text-valign': 'center',
'text-halign': 'center',
'font-size': '10px',
width: 40,
height: 40,
},
},
{
selector: 'node.router',
style: {
'background-color': '#3b82f6',
shape: 'diamond',
},
},
{
selector: 'node.server',
style: {
'background-color': '#22c55e',
shape: 'rectangle',
},
},
{
selector: 'edge',
style: {
width: 2,
'line-color': '#3a3a3a',
'target-arrow-color': '#3a3a3a',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
},
},
],
layout: {
name: 'cose',
animate: true,
animationDuration: 500,
nodeDimensionsIncludeLabels: true,
},
});
// Handle node clicks
cyRef.current.on('tap', 'node', (evt) => {
const node = evt.target;
onHostSelect(node.data());
});
return () => {
if (cyRef.current) {
cyRef.current.destroy();
}
};
}, [hosts, onHostSelect]);
return (
<div
ref={containerRef}
className="network-map-container w-full h-full min-h-[500px] rounded border border-sp-grey-mid"
/>
);
};
export default NetworkMap;

View File

@@ -0,0 +1,124 @@
import React, { useState, useRef, useEffect } from 'react';
const VoiceControls = ({ onCommand = () => {}, apiUrl = '/api/voice' }) => {
const [state, setState] = useState('idle'); // idle, listening, processing
const [transcript, setTranscript] = useState('');
const [hotkey, setHotkey] = useState('`'); // backtick
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const hotkeyPressedRef = useRef(false);
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === hotkey && !hotkeyPressedRef.current && state === 'idle') {
hotkeyPressedRef.current = true;
startRecording();
}
};
const handleKeyUp = (e) => {
if (e.key === hotkey && hotkeyPressedRef.current) {
hotkeyPressedRef.current = false;
stopRecording();
}
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keyup', handleKeyUp);
};
}, [state, hotkey]);
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorderRef.current = new MediaRecorder(stream);
audioChunksRef.current = [];
mediaRecorderRef.current.ondataavailable = (event) => {
audioChunksRef.current.push(event.data);
};
mediaRecorderRef.current.onstop = async () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' });
await sendToTranscribe(audioBlob);
stream.getTracks().forEach((track) => track.stop());
};
mediaRecorderRef.current.start();
setState('listening');
setTranscript('Listening...');
} catch (error) {
console.error('Microphone access denied:', error);
setTranscript('Microphone access denied');
}
};
const stopRecording = () => {
if (mediaRecorderRef.current && state === 'listening') {
mediaRecorderRef.current.stop();
setState('processing');
setTranscript('Processing...');
}
};
const sendToTranscribe = async (audioBlob) => {
try {
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.wav');
const response = await fetch(`${apiUrl}/transcribe`, {
method: 'POST',
body: formData,
});
const result = await response.json();
setTranscript(result.text || 'No speech detected');
setState('idle');
if (result.text) {
onCommand(result.text);
}
} catch (error) {
console.error('Transcription failed:', error);
setTranscript('Transcription failed');
setState('idle');
}
};
return (
<div className="voice-controls p-4 bg-sp-grey rounded border border-sp-grey-mid">
<div className="flex items-center gap-3">
<button
className={`voice-btn w-12 h-12 rounded-full flex items-center justify-center text-2xl transition ${
state === 'listening'
? 'bg-sp-red animate-pulse'
: state === 'processing'
? 'bg-yellow-500'
: 'bg-sp-grey-mid hover:bg-sp-red'
}`}
onMouseDown={startRecording}
onMouseUp={stopRecording}
disabled={state === 'processing'}
>
{state === 'listening' ? '🎙️' : state === 'processing' ? '⏳' : '🎤'}
</button>
<div className="flex-1">
<div className="text-sm text-sp-white-muted">
{state === 'idle' && `Press & hold ${hotkey} or click to speak`}
{state === 'listening' && 'Release to stop recording'}
{state === 'processing' && 'Processing audio...'}
</div>
{transcript && (
<div className="text-sm text-sp-white mt-1 font-mono">{transcript}</div>
)}
</div>
</div>
</div>
);
};
export default VoiceControls;

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import VoiceControls from './VoiceControls';
import NetworkMap from './NetworkMap';
import GuidedWizard from './GuidedWizard';
// Export components for external mounting
window.GooseStrikeComponents = {
VoiceControls,
NetworkMap,
GuidedWizard,
mount: {
voiceControls: (containerId, props = {}) => {
const container = document.getElementById(containerId);
if (container) {
const root = createRoot(container);
root.render(<VoiceControls {...props} />);
return root;
}
},
networkMap: (containerId, props = {}) => {
const container = document.getElementById(containerId);
if (container) {
const root = createRoot(container);
root.render(<NetworkMap {...props} />);
return root;
}
},
guidedWizard: (containerId, props = {}) => {
const container = document.getElementById(containerId);
if (container) {
const root = createRoot(container);
root.render(<GuidedWizard {...props} />);
return root;
}
},
},
};
export { VoiceControls, NetworkMap, GuidedWizard };

View File

@@ -0,0 +1,19 @@
{
"name": "goosestrike-dashboard",
"version": "0.2.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"cytoscape": "^3.28.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.0.10"
}
}

View File

@@ -3,3 +3,4 @@ uvicorn[standard]==0.32.1
httpx==0.28.1
pydantic==2.10.2
jinja2==3.1.4
websockets==12.0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'static/dist',
emptyOutDir: true,
rollupOptions: {
input: {
components: path.resolve(__dirname, 'components/index.jsx'),
},
output: {
entryFileNames: 'components.js',
chunkFileNames: 'components-[name].js',
assetFileNames: 'components-[name].[ext]',
},
},
},
});

View File

@@ -34,7 +34,7 @@ KALI_EXECUTOR_URL = os.getenv("KALI_EXECUTOR_URL", "http://strikepackage-kali-ex
# Default LLM Configuration (can be overridden via environment or API)
DEFAULT_LLM_PROVIDER = os.getenv("DEFAULT_LLM_PROVIDER", "ollama")
DEFAULT_LLM_MODEL = os.getenv("DEFAULT_LLM_MODEL", "llama3.1:latest")
DEFAULT_LLM_MODEL = os.getenv("DEFAULT_LLM_MODEL", "llama3.2")
# In-memory storage (use Redis in production)
tasks: Dict[str, Any] = {}

View File

@@ -4,6 +4,7 @@ Executes commands in the Kali container via Docker SDK.
"""
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
import docker
@@ -13,6 +14,8 @@ import os
import uuid
import json
import re
import httpx
import xml.etree.ElementTree as ET
from datetime import datetime
from contextlib import asynccontextmanager
@@ -101,6 +104,307 @@ def validate_command(command: str) -> tuple[bool, str]:
return True, "OK"
# Dashboard URL for sending discovered hosts
DASHBOARD_URL = os.getenv("DASHBOARD_URL", "http://dashboard:8080")
def is_nmap_command(command: str) -> bool:
"""Check if command is an nmap scan that might discover hosts."""
parts = command.strip().split()
if not parts:
return False
base_cmd = parts[0].split("/")[-1]
return base_cmd == "nmap" or base_cmd == "masscan"
def detect_os_type(os_string: str) -> str:
"""Detect OS type from nmap OS string."""
if not os_string:
return ""
os_lower = os_string.lower()
if "windows" in os_lower:
return "Windows"
elif any(x in os_lower for x in ["linux", "ubuntu", "debian", "centos", "red hat"]):
return "Linux"
elif any(x in os_lower for x in ["mac os", "darwin", "apple", "ios"]):
return "macOS"
elif "cisco" in os_lower:
return "Cisco Router"
elif "juniper" in os_lower:
return "Juniper Router"
elif any(x in os_lower for x in ["fortinet", "fortigate"]):
return "Fortinet"
elif any(x in os_lower for x in ["vmware", "esxi"]):
return "VMware Server"
elif "freebsd" in os_lower:
return "FreeBSD"
elif "android" in os_lower:
return "Android"
elif any(x in os_lower for x in ["printer", "hp"]):
return "Printer"
elif "switch" in os_lower:
return "Network Switch"
elif "router" in os_lower:
return "Router"
return ""
def infer_os_from_ports(ports: List[Dict]) -> str:
"""Infer OS type from open ports.
Uses a scoring system to handle hosts running multiple services
(e.g., Linux with Samba looks like Windows on port 445).
"""
port_nums = {p["port"] for p in ports}
services = {p.get("service", "").lower() for p in ports}
products = [p.get("product", "").lower() for p in ports]
# Score-based detection to handle mixed indicators
linux_score = 0
windows_score = 0
# Strong Linux indicators
if 22 in port_nums: # SSH is strongly Linux/Unix
linux_score += 3
if any("openssh" in p or "linux" in p for p in products):
linux_score += 5
if any("apache" in p or "nginx" in p for p in products):
linux_score += 2
# Strong Windows indicators
if 135 in port_nums: # MSRPC is Windows-only
windows_score += 5
if 3389 in port_nums: # RDP is Windows
windows_score += 3
if 5985 in port_nums or 5986 in port_nums: # WinRM is Windows-only
windows_score += 5
if any("microsoft" in p or "windows" in p for p in products):
windows_score += 5
# Weak indicators (could be either)
if 445 in port_nums: # SMB - could be Samba on Linux or Windows
windows_score += 1 # Slight Windows bias but not definitive
if 139 in port_nums: # NetBIOS - same as above
windows_score += 1
# Decide based on score
if linux_score > windows_score:
return "Linux"
if windows_score > linux_score:
return "Windows"
# Network device indicators
if 161 in port_nums or 162 in port_nums:
return "Network Device"
# Printer
if 9100 in port_nums or 631 in port_nums:
return "Printer"
return ""
def parse_nmap_output(stdout: str) -> List[Dict[str, Any]]:
"""Parse nmap output (XML or text) and extract discovered hosts."""
hosts = []
# Try XML parsing first (if -oX - was used or combined with other options)
if '<?xml' in stdout or '<nmaprun' in stdout:
try:
xml_start = stdout.find('<?xml')
if xml_start == -1:
xml_start = stdout.find('<nmaprun')
if xml_start != -1:
xml_output = stdout[xml_start:]
hosts = parse_nmap_xml(xml_output)
if hosts:
return hosts
except Exception as e:
print(f"XML parsing failed: {e}")
# Fallback to text parsing
hosts = parse_nmap_text(stdout)
return hosts
def parse_nmap_xml(xml_output: str) -> List[Dict[str, Any]]:
"""Parse nmap XML output to extract hosts."""
hosts = []
try:
root = ET.fromstring(xml_output)
for host_elem in root.findall('.//host'):
status = host_elem.find("status")
if status is None or status.get("state") != "up":
continue
host = {
"ip": "",
"hostname": "",
"mac": "",
"vendor": "",
"os_type": "",
"os_details": "",
"ports": []
}
# Get IP address
addr = host_elem.find("address[@addrtype='ipv4']")
if addr is not None:
host["ip"] = addr.get("addr", "")
# Get MAC address
mac = host_elem.find("address[@addrtype='mac']")
if mac is not None:
host["mac"] = mac.get("addr", "")
host["vendor"] = mac.get("vendor", "")
# Get hostname
hostname = host_elem.find(".//hostname")
if hostname is not None:
host["hostname"] = hostname.get("name", "")
# Get OS info
os_elem = host_elem.find(".//osmatch")
if os_elem is not None:
os_name = os_elem.get("name", "")
host["os_details"] = os_name
host["os_type"] = detect_os_type(os_name)
# Get ports
for port_elem in host_elem.findall(".//port"):
state_elem = port_elem.find("state")
port_info = {
"port": int(port_elem.get("portid", 0)),
"protocol": port_elem.get("protocol", "tcp"),
"state": state_elem.get("state", "") if state_elem is not None else "",
"service": ""
}
service = port_elem.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", "")
if port_info["state"] == "open":
host["ports"].append(port_info)
# Infer OS from ports if still unknown
if not host["os_type"] and host["ports"]:
host["os_type"] = infer_os_from_ports(host["ports"])
# Only include hosts with at least one OPEN port
# This prevents false positives from proxy ARP responses
# where routers respond for all IPs even if device is offline
if host["ip"] and host["ports"]:
hosts.append(host)
except ET.ParseError as e:
print(f"XML parse error: {e}")
return hosts
def parse_nmap_text(output: str) -> List[Dict[str, Any]]:
"""Parse nmap text output as fallback.
Only returns hosts that have at least one OPEN port.
Filters out false positives from router proxy ARP (where all IPs appear "up").
"""
hosts = []
current_host = None
def save_host_if_has_open_ports(host):
"""Only save host if it has at least one open port."""
if host and host.get("ip") and host.get("ports"):
# Infer OS before saving
if not host["os_type"]:
host["os_type"] = infer_os_from_ports(host["ports"])
hosts.append(host)
for line in output.split('\n'):
# Match host line: "Nmap scan report for hostname (IP)" or "Nmap scan report for IP"
host_match = re.search(r'Nmap scan report for (?:(\S+) \()?(\d+\.\d+\.\d+\.\d+)', line)
if host_match:
# Save previous host only if it has open ports
save_host_if_has_open_ports(current_host)
current_host = {
"ip": host_match.group(2),
"hostname": host_match.group(1) or "",
"os_type": "",
"os_details": "",
"ports": [],
"mac": "",
"vendor": ""
}
continue
if current_host:
# Match MAC: "MAC Address: XX:XX:XX:XX:XX:XX (Vendor Name)"
mac_match = re.search(r'MAC Address: ([0-9A-Fa-f:]+)(?: \(([^)]+)\))?', line)
if mac_match:
current_host["mac"] = mac_match.group(1)
current_host["vendor"] = mac_match.group(2) or ""
# Match port: "80/tcp open http Apache httpd"
port_match = re.search(r'(\d+)/(tcp|udp)\s+(\w+)\s+(\S+)(?:\s+(.*))?', line)
if port_match and port_match.group(3) == "open":
port_info = {
"port": int(port_match.group(1)),
"protocol": port_match.group(2),
"state": "open",
"service": port_match.group(4),
"product": port_match.group(5) or ""
}
current_host["ports"].append(port_info)
# Match OS: "OS details: Linux 4.15 - 5.6" or "Running: Linux"
os_match = re.search(r'(?:OS details?|Running):\s*(.+)', line)
if os_match:
current_host["os_details"] = os_match.group(1)
current_host["os_type"] = detect_os_type(os_match.group(1))
# Match "Service Info: OS: Linux" style
service_os_match = re.search(r'Service Info:.*OS:\s*([^;,]+)', line)
if service_os_match and not current_host["os_type"]:
current_host["os_type"] = detect_os_type(service_os_match.group(1))
# Match "Aggressive OS guesses: Linux 5.4 (98%)" - take first high confidence
aggressive_match = re.search(r'Aggressive OS guesses:\s*([^(]+)\s*\((\d+)%\)', line)
if aggressive_match and not current_host["os_details"]:
confidence = int(aggressive_match.group(2))
if confidence >= 85:
current_host["os_details"] = aggressive_match.group(1).strip()
current_host["os_type"] = detect_os_type(aggressive_match.group(1))
# Don't forget the last host - only if it has open ports
save_host_if_has_open_ports(current_host)
return hosts
async def send_hosts_to_dashboard(hosts: List[Dict[str, Any]]):
"""Send discovered hosts to the dashboard for network map update."""
if not hosts:
return
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.post(
f"{DASHBOARD_URL}/api/network/hosts/discover",
json={"hosts": hosts, "source": "terminal"}
)
if response.status_code == 200:
result = response.json()
print(f"Sent {len(hosts)} hosts to dashboard: added={result.get('added')}, updated={result.get('updated')}")
else:
print(f"Failed to send hosts to dashboard: {response.status_code}")
except Exception as e:
print(f"Error sending hosts to dashboard: {e}")
# Docker client
docker_client = None
kali_container = None
@@ -253,6 +557,23 @@ def _run_command_sync(container, command, working_dir):
workdir=working_dir
)
@app.get("/stream/processes")
async def stream_running_processes():
"""Server-Sent Events stream of running security processes.
Emits JSON events with current process list every 5 seconds.
"""
async def event_generator():
while True:
try:
data = await get_running_processes()
yield f"data: {json.dumps(data)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
await asyncio.sleep(5)
return StreamingResponse(event_generator(), media_type="text/event-stream")
@app.post("/execute", response_model=CommandResult)
async def execute_command(request: CommandRequest):
"""Execute a command in the Kali container."""
@@ -292,6 +613,15 @@ async def execute_command(request: CommandRequest):
stdout = output[0].decode('utf-8', errors='replace') if output[0] else ""
stderr = output[1].decode('utf-8', errors='replace') if output[1] else ""
# Parse nmap output and send hosts to dashboard for network map
if is_nmap_command(request.command) and stdout:
try:
hosts = parse_nmap_output(stdout)
if hosts:
asyncio.create_task(send_hosts_to_dashboard(hosts))
except Exception as e:
print(f"Error parsing nmap output: {e}")
return CommandResult(
command_id=command_id,
command=request.command,
@@ -309,6 +639,52 @@ async def execute_command(request: CommandRequest):
except Exception as e:
raise HTTPException(status_code=500, detail=f"Execution error: {str(e)}")
@app.websocket("/ws/execute/{command_id}")
async def websocket_execute(websocket: WebSocket, command_id: str):
"""WebSocket endpoint for streaming command output in real-time."""
await websocket.accept()
if command_id not in running_commands:
await websocket.send_json({"error": "Command not found"})
await websocket.close()
return
cmd_info = running_commands[command_id]
try:
# Stream output as it becomes available
last_stdout_len = 0
last_stderr_len = 0
while cmd_info["status"] == "running":
current_stdout = cmd_info.get("stdout", "")
current_stderr = cmd_info.get("stderr", "")
# Send new stdout
if len(current_stdout) > last_stdout_len:
new_stdout = current_stdout[last_stdout_len:]
await websocket.send_json({"type": "stdout", "data": new_stdout})
last_stdout_len = len(current_stdout)
# Send new stderr
if len(current_stderr) > last_stderr_len:
new_stderr = current_stderr[last_stderr_len:]
await websocket.send_json({"type": "stderr", "data": new_stderr})
last_stderr_len = len(current_stderr)
await asyncio.sleep(0.5)
# Send final status
await websocket.send_json({
"type": "complete",
"status": cmd_info["status"],
"exit_code": cmd_info.get("exit_code"),
"duration": cmd_info.get("duration_seconds"),
})
except WebSocketDisconnect:
pass
finally:
await websocket.close()
@app.post("/execute/async")
async def execute_command_async(request: CommandRequest):
@@ -420,18 +796,92 @@ async def websocket_execute(websocket: WebSocket):
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')
})
# Collect output for nmap parsing
full_stdout = []
is_nmap = is_nmap_command(command)
# Stream output with keepalive for long-running commands
last_output_time = asyncio.get_event_loop().time()
output_queue = asyncio.Queue()
stream_complete = asyncio.Event()
# Synchronous function to read from Docker stream (runs in thread)
def read_docker_output_sync(queue: asyncio.Queue, loop, complete_event):
try:
for stdout, stderr in exec_result.output:
if stdout:
asyncio.run_coroutine_threadsafe(
queue.put(("stdout", stdout.decode('utf-8', errors='replace'))),
loop
)
if stderr:
asyncio.run_coroutine_threadsafe(
queue.put(("stderr", stderr.decode('utf-8', errors='replace'))),
loop
)
except Exception as e:
asyncio.run_coroutine_threadsafe(
queue.put(("error", str(e))),
loop
)
finally:
loop.call_soon_threadsafe(complete_event.set)
# Start reading in background thread
loop = asyncio.get_event_loop()
read_future = loop.run_in_executor(
executor,
read_docker_output_sync,
output_queue,
loop,
stream_complete
)
# Send output and keepalives
keepalive_interval = 25 # seconds
while not stream_complete.is_set() or not output_queue.empty():
try:
# Wait for output with timeout for keepalive
try:
msg_type, msg_data = await asyncio.wait_for(
output_queue.get(),
timeout=keepalive_interval
)
last_output_time = asyncio.get_event_loop().time()
if msg_type == "stdout":
if is_nmap:
full_stdout.append(msg_data)
await websocket.send_json({"type": "stdout", "data": msg_data})
elif msg_type == "stderr":
await websocket.send_json({"type": "stderr", "data": msg_data})
elif msg_type == "error":
await websocket.send_json({"type": "error", "message": msg_data})
except asyncio.TimeoutError:
# No output for a while, send keepalive
elapsed = asyncio.get_event_loop().time() - last_output_time
await websocket.send_json({
"type": "keepalive",
"elapsed": int(elapsed),
"message": f"Scan in progress ({int(elapsed)}s)..."
})
except Exception as e:
print(f"Error in output loop: {e}")
break
# Wait for read thread to complete
await read_future
# Parse nmap output and send hosts to dashboard
if is_nmap and full_stdout:
try:
combined_output = "".join(full_stdout)
hosts = parse_nmap_output(combined_output)
if hosts:
asyncio.create_task(send_hosts_to_dashboard(hosts))
except Exception as e:
print(f"Error parsing nmap output: {e}")
await websocket.send_json({
"type": "complete",

View File

@@ -3,3 +3,4 @@ uvicorn[standard]==0.32.1
docker==7.1.0
pydantic==2.10.2
websockets==14.1
httpx==0.28.1

View File

@@ -3,56 +3,45 @@ FROM kalilinux/kali-rolling
# Avoid prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive
# Configure apt to use direct Kali mirrors (avoid CDN mirrors that get blocked by content filters)
# The mirror.us.cdn-perfprod.com CDN is being blocked by SafeBrowse content filter
RUN echo 'deb http://kali.download/kali kali-rolling main non-free non-free-firmware contrib' > /etc/apt/sources.list && \
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries
# Configure apt to use reliable mirrors and retry on failure
RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::http::Timeout "30";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'deb http://kali.download/kali kali-rolling main non-free non-free-firmware contrib' > /etc/apt/sources.list
# Update and install essential security tools
RUN apt-get update && apt-get install -y --no-install-recommends \
nmap \
nikto \
sqlmap \
gobuster \
dirb \
wfuzz \
hydra \
john \
hashcat \
whois \
dnsutils \
net-tools \
iputils-ping \
curl \
wget \
git \
vim \
jq \
uuid-runtime \
python3 \
python3-pip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install kali-linux-everything metapackage (600+ tools, ~15GB)
# This includes: nmap, metasploit, burpsuite, wireshark, aircrack-ng,
# hashcat, john, hydra, sqlmap, nikto, wpscan, responder, crackmapexec,
# enum4linux, gobuster, dirb, wfuzz, masscan, and hundreds more
RUN apt-get update && \
apt-get install -y kali-linux-everything && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install additional Python tools for command logging and scripting
RUN pip3 install --break-system-packages \
# Install additional Python tools and utilities for command logging
# Install setuptools first to fix compatibility issues with Python 3.13
RUN pip3 install --break-system-packages setuptools wheel && \
pip3 install --break-system-packages \
requests \
beautifulsoup4 \
shodan \
censys
# Install jq and uuid-runtime for command logging
RUN apt-get update && apt-get install -y --no-install-recommends \
jq \
uuid-runtime \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Create workspace directory
WORKDIR /workspace
# Copy scripts and convert Windows line endings to Unix
# Copy scripts and fix line endings (in case of Windows CRLF)
COPY entrypoint.sh /entrypoint.sh
COPY command_logger.sh /usr/local/bin/command_logger.sh
COPY capture_wrapper.sh /usr/local/bin/capture
COPY nmap_wrapper.sh /usr/local/bin/nmap
# Convert any Windows line endings (CRLF) to Unix (LF)
RUN sed -i 's/\r$//' /entrypoint.sh /usr/local/bin/command_logger.sh /usr/local/bin/capture /usr/local/bin/nmap
RUN chmod +x /entrypoint.sh /usr/local/bin/command_logger.sh /usr/local/bin/capture /usr/local/bin/nmap
RUN sed -i 's/\r$//' /entrypoint.sh /usr/local/bin/command_logger.sh /usr/local/bin/capture && \
chmod +x /entrypoint.sh /usr/local/bin/command_logger.sh /usr/local/bin/capture
# Create command history directory
RUN mkdir -p /workspace/.command_history

View File

@@ -1,10 +1,8 @@
#!/bin/bash
# Output Capture Wrapper for Security Tools
# Wraps command execution to capture stdout/stderr and save results
# Automatically sends nmap results to dashboard network map
COMMAND_LOG_DIR="${COMMAND_LOG_DIR:-/workspace/.command_history}"
DASHBOARD_URL="${DASHBOARD_URL:-http://strikepackage-dashboard:8080}"
mkdir -p "$COMMAND_LOG_DIR"
# Get command from arguments
@@ -63,35 +61,12 @@ cat > "$output_file" << EOF
}
EOF
# Output results to terminal first
echo "$stdout_content"
[ -n "$stderr_content" ] && echo "$stderr_content" >&2
# Clean up temp files
rm -f "$stdout_file" "$stderr_file"
# If this was an nmap command, send results to dashboard network map
if [[ "$cmd_string" == nmap* ]] && [ $exit_code -eq 0 ]; then
echo "" >&2
echo "[StrikePackageGPT] Detected nmap scan, sending to Network Map..." >&2
# Send nmap output to dashboard for parsing
nmap_json=$(jq -n --arg output "$stdout_content" --arg source "terminal" \
'{output: $output, source: $source}')
response=$(curl -s -X POST "${DASHBOARD_URL}/api/network/nmap-results" \
-H "Content-Type: application/json" \
-d "$nmap_json" 2>/dev/null || echo '{"error":"failed to connect"}')
# Parse response
added=$(echo "$response" | jq -r '.added // 0' 2>/dev/null)
updated=$(echo "$response" | jq -r '.updated // 0' 2>/dev/null)
total=$(echo "$response" | jq -r '.total // 0' 2>/dev/null)
if [ "$added" != "null" ] && [ "$added" != "0" -o "$updated" != "0" ]; then
echo "[StrikePackageGPT] Network Map updated: $added added, $updated updated (total: $total hosts)" >&2
fi
fi
# Output results to terminal
cat "$stdout_file" 2>/dev/null || true
cat "$stderr_file" >&2 2>/dev/null || true
echo "" >&2
echo "[StrikePackageGPT] Command captured: $cmd_id" >&2

View File

@@ -3,48 +3,12 @@
# Enable command logging by default for all bash sessions
echo 'source /usr/local/bin/command_logger.sh' >> /root/.bashrc
echo 'export COMMAND_LOG_DIR=/workspace/.command_history' >> /root/.bashrc
echo 'export DASHBOARD_URL=http://strikepackage-dashboard:8080' >> /root/.bashrc
# Create convenience aliases for captured execution
cat >> /root/.bashrc << 'ALIASES'
# Convenience alias to run commands with automatic capture
alias run='capture'
# Wrap nmap to automatically send results to network map
nmap_wrapper() {
local output
local exit_code
# Run nmap and capture output
output=$(/usr/bin/nmap "$@" 2>&1)
exit_code=$?
# Display output
echo "$output"
# If successful, send to dashboard network map
if [ $exit_code -eq 0 ]; then
echo "" >&2
echo "[StrikePackageGPT] Sending nmap results to Network Map..." >&2
# Send to dashboard
response=$(curl -s -X POST "${DASHBOARD_URL:-http://strikepackage-dashboard:8080}/api/network/nmap-results" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg output "$output" --arg source "terminal" '{output: $output, source: $source}')" 2>/dev/null)
added=$(echo "$response" | jq -r '.added // 0' 2>/dev/null)
updated=$(echo "$response" | jq -r '.updated // 0' 2>/dev/null)
total=$(echo "$response" | jq -r '.total // 0' 2>/dev/null)
if [ "$added" != "null" ] 2>/dev/null; then
echo "[StrikePackageGPT] Network Map: $added added, $updated updated (total: $total hosts)" >&2
fi
fi
return $exit_code
}
alias nmap='nmap_wrapper'
# Helper function to show recent commands
recent_commands() {
echo "Recent commands logged:"
@@ -54,42 +18,36 @@ recent_commands() {
done
}
alias recent='recent_commands'
# Show network map hosts
show_hosts() {
echo "Network Map Hosts:"
curl -s "${DASHBOARD_URL:-http://strikepackage-dashboard:8080}/api/network/hosts" | jq -r '.hosts[] | "\(.ip)\t\(.hostname // "-")\t\(.os // "-")\tPorts: \(.ports | length)"' 2>/dev/null || echo "No hosts found"
}
alias hosts='show_hosts'
# Clear network map
clear_hosts() {
curl -s -X DELETE "${DASHBOARD_URL:-http://strikepackage-dashboard:8080}/api/network/hosts" | jq .
echo "Network map cleared"
}
ALIASES
echo "=================================================="
echo " StrikePackageGPT - Kali Container"
echo " Security Tools Ready + Network Map Integration"
echo " Security Tools Ready + Command Capture Enabled"
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 " - aircrack-ng, wifite (wireless)"
echo " - john, hashcat (password cracking)"
echo " - and 600+ more Kali tools"
echo ""
echo "🗺️ NETWORK MAP INTEGRATION ENABLED 🗺️"
echo "🔄 BIDIRECTIONAL CAPTURE ENABLED 🔄"
echo ""
echo "nmap scans automatically appear in the Dashboard Network Map!"
echo "Commands you run here will be captured and visible in:"
echo " • Dashboard history"
echo " • API scan results"
echo " • Network visualization"
echo ""
echo "Commands:"
echo " • nmap -sV 192.168.1.1 - Scan and auto-add to map"
echo " • hosts - Show network map hosts"
echo " • clear_hosts - Clear network map"
echo " • recent - Show recent commands"
echo "Usage:"
echo " • Run commands normally: nmap -sV 192.168.1.1"
echo " • Use 'capture' prefix for explicit capture: capture nmap -sV 192.168.1.1"
echo " • View recent: recent"
echo ""
echo "Container is ready for security testing."
echo ""

View File

@@ -1,50 +0,0 @@
#!/bin/bash
# StrikePackageGPT nmap wrapper - sends scan results to Network Map and Scan History automatically
DASHBOARD_URL="${DASHBOARD_URL:-http://strikepackage-dashboard:8080}"
REAL_NMAP="/usr/bin/nmap"
# Capture the full command for logging
full_command="nmap $*"
# Determine target (last non-flag argument)
target="unknown"
for arg in "$@"; do
if [[ ! "$arg" =~ ^- ]]; then
target="$arg"
fi
done
# Create temp file for output
tmpfile=$(mktemp)
trap "rm -f $tmpfile" EXIT
# Run the actual nmap and capture output
"$REAL_NMAP" "$@" 2>&1 | tee "$tmpfile"
exit_code=${PIPESTATUS[0]}
# If successful, send to dashboard
if [ $exit_code -eq 0 ]; then
echo "" >&2
echo "[StrikePackageGPT] Sending results to Dashboard..." >&2
# Use jq with file input to avoid argument length limits
# Send to network map
jq -Rs --arg source "terminal" '{output: ., source: $source}' "$tmpfile" | \
curl -s -X POST "${DASHBOARD_URL}/api/network/nmap-results" \
-H "Content-Type: application/json" \
-d @- >/dev/null 2>&1
# Send to scan history
response=$(jq -Rs --arg tool "nmap" --arg target "$target" --arg command "$full_command" \
'{tool: $tool, target: $target, command: $command, output: ., source: "terminal"}' "$tmpfile" | \
curl -s -X POST "${DASHBOARD_URL}/api/scans/terminal" \
-H "Content-Type: application/json" \
-d @- 2>/dev/null)
if [ -n "$response" ]; then
echo "[StrikePackageGPT] ✓ Results saved to Network Map and Scan History" >&2
fi
fi
exit $exit_code

View File

@@ -1,121 +0,0 @@
#!/bin/bash
# StrikePackageGPT Service Test Script
echo "=========================================="
echo " StrikePackageGPT V2.1 Test Suite"
echo "=========================================="
echo ""
# Test 1: Health Endpoints
echo "=== TEST 1: Health Endpoints ==="
echo "LLM Router:"
curl -s http://strikepackage-llm-router:8000/health | jq .
echo ""
echo "HackGPT API:"
curl -s http://strikepackage-hackgpt-api:8001/health | jq .
echo ""
echo "Kali Executor:"
curl -s http://strikepackage-kali-executor:8002/health | jq .
echo ""
# Test 2: LLM Router
echo "=== TEST 2: LLM Router ==="
echo "Providers:"
curl -s http://strikepackage-llm-router:8000/providers | jq -r 'keys[]'
echo ""
echo "Chat Test (llama3.1:latest):"
cat > /tmp/chat.json << 'EOFCHAT'
{"provider":"ollama","model":"llama3.1:latest","messages":[{"role":"user","content":"Say hello in exactly 3 words"}]}
EOFCHAT
RESPONSE=$(curl -s -X POST http://strikepackage-llm-router:8000/chat -H "Content-Type: application/json" -d @/tmp/chat.json)
echo "$RESPONSE" | jq -r '.response // .content // .message // .' | head -c 200
echo ""
echo ""
# Test 3: HackGPT API
echo "=== TEST 3: HackGPT API ==="
echo "Tools available:"
curl -s http://strikepackage-hackgpt-api:8001/tools | jq -r '.[].name' 2>/dev/null || curl -s http://strikepackage-hackgpt-api:8001/tools | head -c 300
echo ""
echo "Scans list:"
curl -s http://strikepackage-hackgpt-api:8001/scans | jq . 2>/dev/null || echo "[]"
echo ""
# Test 4: Kali Executor
echo "=== TEST 4: Kali Executor ==="
echo "Tools:"
curl -s http://strikepackage-kali-executor:8002/tools | jq -r '.tools[:5][]' 2>/dev/null || curl -s http://strikepackage-kali-executor:8002/tools | head -c 200
echo ""
echo "Execute nmap version:"
cat > /tmp/exec.json << 'EOFEXEC'
{"command":"nmap --version","timeout":30}
EOFEXEC
curl -s -X POST http://strikepackage-kali-executor:8002/execute -H "Content-Type: application/json" -d @/tmp/exec.json | jq -r '.output // .stdout // .' | head -5
echo ""
echo "Jobs:"
curl -s http://strikepackage-kali-executor:8002/jobs | jq -r 'length' 2>/dev/null || echo "0"
echo " jobs recorded"
echo ""
# Test 5: Security Scans from Kali
echo "=== TEST 5: Security Tools in Kali ==="
echo "nmap scan of llm-router:"
nmap -sT -p 8000 strikepackage-llm-router 2>&1 | grep -E "PORT|open|closed"
echo ""
echo "nikto quick test:"
nikto -h http://strikepackage-dashboard:8080 -maxtime 10s 2>&1 | tail -5
echo ""
echo "gobuster test:"
gobuster dir -u http://strikepackage-hackgpt-api:8001 -w /usr/share/dirb/wordlists/common.txt -t 5 --timeout 5s 2>&1 | grep -E "Status|Found|Finished" | head -10
echo ""
# Test 6: Dashboard APIs
echo "=== TEST 6: Dashboard APIs ==="
echo "Status:"
curl -s http://strikepackage-dashboard:8080/api/status | jq . 2>/dev/null || curl -s http://strikepackage-dashboard:8080/api/status
echo ""
echo "Processes:"
curl -s http://strikepackage-dashboard:8080/api/processes | jq -r 'length' 2>/dev/null
echo " processes"
echo ""
# Test 7: End-to-end Chat via Dashboard
echo "=== TEST 7: Dashboard Chat ==="
cat > /tmp/dashchat.json << 'EOFDASH'
{"message":"What is nmap used for? Answer in one sentence."}
EOFDASH
CHATRESP=$(curl -s -X POST http://strikepackage-dashboard:8080/api/chat -H "Content-Type: application/json" -d @/tmp/dashchat.json)
echo "$CHATRESP" | jq -r '.response // .content // .message // .' 2>/dev/null | head -c 300
echo ""
echo ""
# Test 8: Command Execution via Kali Executor
echo "=== TEST 8: Command Execution ==="
cat > /tmp/scanexec.json << 'EOFSCAN'
{"command":"nmap -sT -p 80,443,8000,8080 strikepackage-dashboard","timeout":60}
EOFSCAN
echo "Running: nmap scan of dashboard..."
EXECRESP=$(curl -s -X POST http://strikepackage-kali-executor:8002/execute -H "Content-Type: application/json" -d @/tmp/scanexec.json)
echo "$EXECRESP" | jq -r '.output // .stdout // .' 2>/dev/null | grep -E "PORT|open|closed|filtered" | head -10
echo ""
# Test 9: AI-Assisted Scanning
echo "=== TEST 9: AI Scan Request ==="
cat > /tmp/aiscan.json << 'EOFAI'
{"message":"Scan strikepackage-llm-router for web vulnerabilities"}
EOFAI
AIRESP=$(curl -s -X POST http://strikepackage-dashboard:8080/api/chat -H "Content-Type: application/json" -d @/tmp/aiscan.json)
echo "$AIRESP" | jq -r '.response // .content // .' 2>/dev/null | head -c 400
echo ""
echo ""
echo "=========================================="
echo " Tests Complete!"
echo "=========================================="