Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Thumbs.db

# Logs - workflow logs are tracked in git for session history
# log/**/*.md # Removed - workflow logs should be committed
*.log
!log/**/*.md

# Test artifacts and temporary files
*.db
test_*.py # Exclude test scripts in root (tests/ directory is fine)

# AKIS session tracking (temporary file for VSCode extension)
.akis-session.json
1 change: 0 additions & 1 deletion agent.py

This file was deleted.

16 changes: 12 additions & 4 deletions backend/app/api/v1/endpoints/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import os
import shutil
from datetime import datetime
from app.core.security import get_current_user
from app.core.security import get_current_user, decode_token

router = APIRouter()

Expand Down Expand Up @@ -455,13 +455,21 @@ async def terminal_websocket(websocket: WebSocket, token: Optional[str] = None):
import termios
import select

# Basic token validation
# Verify JWT token
if not token:
await websocket.close(code=1008, reason="Authentication required")
return

# TODO: Add proper JWT token verification here
# For now, just check that token is provided
try:
# Decode and verify JWT token
payload = decode_token(token)
user_id = payload.get("sub")
if not user_id:
await websocket.close(code=1008, reason="Invalid token")
return
except ValueError as e:
await websocket.close(code=1008, reason=f"Token validation failed: {str(e)}")
return

await websocket.accept()

Expand Down
28 changes: 9 additions & 19 deletions backend/app/services/PingService.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import subprocess
import re
import time
import ipaddress
from typing import Optional, Dict, List, Any
from datetime import datetime
from app.utils.validators import NetworkValidator, InputValidator


class PingService:
Expand Down Expand Up @@ -36,27 +36,17 @@ def _validate_target(self, target: str) -> str:
if not target:
raise ValueError("Target cannot be empty")

# Try to parse as IP address first
try:
ipaddress.ip_address(target)
# Try to validate as IP address first
is_valid_ip, _ = NetworkValidator.validate_ip_address(target)
if is_valid_ip:
return target
except ValueError:
pass

# Validate as hostname (RFC 1123)
# Allow alphanumeric, hyphens, dots, max 253 chars
if len(target) > 253:
raise ValueError("Hostname too long")

# Check for valid hostname pattern
hostname_pattern = re.compile(
r'^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$'
)

if not hostname_pattern.match(target):
raise ValueError("Invalid hostname format")
# Validate as hostname
is_valid_hostname, error = InputValidator.validate_hostname(target)
if is_valid_hostname:
return target

return target
raise ValueError(error or "Invalid target format")

async def icmp_ping(
self,
Expand Down
50 changes: 21 additions & 29 deletions backend/app/services/SnifferService.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@
import os
import socket
import struct
from app.utils.validators import NetworkValidator
from app.utils import constants as const

logger = logging.getLogger(__name__)

class SnifferService:
# Constants for packet crafting
PACKET_SEND_TIMEOUT = 3 # seconds
RESPONSE_HEX_MAX_LENGTH = 200 # characters
STORM_THREAD_STOP_TIMEOUT = 2.0 # seconds

def __init__(self):
self.is_sniffing = False
self.capture_thread: Optional[threading.Thread] = None
Expand All @@ -27,7 +24,7 @@ def __init__(self):
self.interface: Optional[str] = None
self.filter: Optional[str] = None
self.captured_packets = []
self.max_stored_packets = 1000
self.max_stored_packets = const.MAX_STORED_PACKETS
self.stats = {
"total_flows": 0,
"total_bytes": 0,
Expand All @@ -40,10 +37,10 @@ def __init__(self):
self.last_bytes_check = 0
self.last_if_stats = {}
self.discovered_hosts = {} # Format: {ip_address: {"first_seen": timestamp, "last_seen": timestamp, "mac_address": mac}}
self.track_source_only = True # Default to safer mode (source IPs only)
self.filter_unicast = False # Don't filter unicast by default (allows passive listener detection)
self.filter_multicast = True # Filter multicast by default
self.filter_broadcast = True # Filter broadcast by default
self.track_source_only = const.DEFAULT_TRACK_SOURCE_ONLY
self.filter_unicast = const.DEFAULT_FILTER_UNICAST
self.filter_multicast = const.DEFAULT_FILTER_MULTICAST
self.filter_broadcast = const.DEFAULT_FILTER_BROADCAST

# Storm-related attributes
self.is_storming = False
Expand Down Expand Up @@ -162,20 +159,16 @@ def _is_broadcast_mac(self, mac: str) -> bool:
return mac.upper() == "FF:FF:FF:FF:FF:FF"

def _is_broadcast_ip(self, ip: str) -> bool:
"""Check if IP is broadcast (ends with .255) or 255.255.255.255"""
return ip.endswith(".255") or ip == "255.255.255.255"
"""Check if IP is broadcast"""
return NetworkValidator.is_broadcast_ip(ip)

def _is_multicast_ip(self, ip: str) -> bool:
"""Check if IP is multicast (224.0.0.0 - 239.255.255.255)"""
try:
first_octet = int(ip.split('.')[0])
return 224 <= first_octet <= 239
except:
return False
"""Check if IP is multicast"""
return NetworkValidator.is_multicast_ip(ip)

def _is_link_local_ip(self, ip: str) -> bool:
"""Check if IP is link-local (169.254.x.x)"""
return ip.startswith("169.254.")
"""Check if IP is link-local"""
return NetworkValidator.is_link_local_ip(ip)

def _is_valid_source_ip(self, ip: str) -> bool:
"""Check if IP is a valid source address (not broadcast, not 0.0.0.0, not link-local)"""
Expand Down Expand Up @@ -941,7 +934,7 @@ def craft_and_send_packet(self, packet_config: Dict[str, Any]) -> Dict[str, Any]
# Single packet mode - wait for response
try:
# sr1 sends packet and receives first response
response = sr1(packet, timeout=self.PACKET_SEND_TIMEOUT, verbose=0)
response = sr1(packet, timeout=const.PACKET_SEND_TIMEOUT, verbose=0)
elapsed = time.time() - start_time

if response:
Expand All @@ -955,7 +948,7 @@ def craft_and_send_packet(self, packet_config: Dict[str, Any]) -> Dict[str, Any]
"source": None,
"destination": None,
"length": len(response),
"raw_hex": response.build().hex()[:self.RESPONSE_HEX_MAX_LENGTH]
"raw_hex": response.build().hex()[:const.RESPONSE_HEX_MAX_LENGTH]
}

if IP in response:
Expand Down Expand Up @@ -989,7 +982,7 @@ def craft_and_send_packet(self, packet_config: Dict[str, Any]) -> Dict[str, Any]
"trace": trace
}
else:
trace.append(f"No response received (timeout: {self.PACKET_SEND_TIMEOUT}s)")
trace.append(f"No response received (timeout: {const.PACKET_SEND_TIMEOUT}s)")
return {
"success": True,
"sent_packet": {
Expand Down Expand Up @@ -1041,18 +1034,17 @@ def start_storm(self, config: Dict[str, Any]) -> Dict[str, Any]:
}

# Validate PPS
if not isinstance(pps, int) or pps < 1 or pps > 10000000:
if not isinstance(pps, int) or pps < const.MIN_PPS or pps > const.MAX_PPS:
return {
"success": False,
"error": "PPS must be between 1 and 10,000,000"
"error": f"PPS must be between {const.MIN_PPS} and {const.MAX_PPS:,}"
}

# Validate packet type
valid_types = ["broadcast", "multicast", "tcp", "udp", "raw_ip"]
if packet_type not in valid_types:
if packet_type not in const.VALID_PACKET_TYPES:
return {
"success": False,
"error": f"Invalid packet type. Must be one of: {', '.join(valid_types)}"
"error": f"Invalid packet type. Must be one of: {', '.join(const.VALID_PACKET_TYPES)}"
}

# Validate ports for TCP/UDP
Expand Down Expand Up @@ -1431,7 +1423,7 @@ def stop_storm(self) -> Dict[str, Any]:
self.is_storming = False

if self.storm_thread:
self.storm_thread.join(timeout=self.STORM_THREAD_STOP_TIMEOUT)
self.storm_thread.join(timeout=const.STORM_THREAD_STOP_TIMEOUT)

return {
"success": True,
Expand Down
52 changes: 52 additions & 0 deletions backend/app/utils/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Configuration constants for network services
"""

# Packet crafting constants
PACKET_SEND_TIMEOUT = 3 # seconds
RESPONSE_HEX_MAX_LENGTH = 200 # characters
STORM_THREAD_STOP_TIMEOUT = 2.0 # seconds

# Packet capture constants
MAX_STORED_PACKETS = 1000
STATS_UPDATE_INTERVAL = 1 # seconds
TRAFFIC_HISTORY_LENGTH = 20 # data points
INTERFACE_HISTORY_LENGTH = 30 # data points

# Storm testing limits
MIN_PPS = 1
MAX_PPS = 10_000_000
VALID_PACKET_TYPES = ["broadcast", "multicast", "tcp", "udp", "raw_ip"]

# Network filtering defaults
DEFAULT_TRACK_SOURCE_ONLY = True
DEFAULT_FILTER_UNICAST = False
DEFAULT_FILTER_MULTICAST = True
DEFAULT_FILTER_BROADCAST = True

# Ping service constants
MIN_PING_COUNT = 1
MAX_PING_COUNT = 100
MIN_TIMEOUT = 1
MAX_TIMEOUT = 30
MIN_PACKET_SIZE = 1
MAX_PACKET_SIZE = 65500

# Port ranges
MIN_PORT = 1
MAX_PORT = 65535

# Protocol defaults
DEFAULT_SSH_PORT = 22
DEFAULT_FTP_PORT = 21
DEFAULT_HTTP_PORT = 80
DEFAULT_HTTPS_PORT = 443
DEFAULT_RDP_PORT = 3389
DEFAULT_VNC_PORT = 5900
DEFAULT_TELNET_PORT = 23
DEFAULT_MYSQL_PORT = 3306
DEFAULT_POSTGRES_PORT = 5432

# Service detection timeouts
CONNECTION_TIMEOUT = 5 # seconds
HTTP_REQUEST_TIMEOUT = 10 # seconds
Loading