Files
Chasing-Your-Tail-NG/secure_ignore_loader.py
2025-07-23 14:48:04 -07:00

175 lines
6.6 KiB
Python

"""
Secure ignore list loader - replaces dangerous exec() calls
"""
import json
import pathlib
import re
from typing import List, Optional
import logging
from input_validation import InputValidator
logger = logging.getLogger(__name__)
class SecureIgnoreLoader:
"""Secure loader for MAC and SSID ignore lists"""
@staticmethod
def validate_mac_address(mac: str) -> bool:
"""Validate MAC address format using secure validator"""
return InputValidator.validate_mac_address(mac)
@staticmethod
def validate_ssid(ssid: str) -> bool:
"""Validate SSID using secure validator"""
return InputValidator.validate_ssid(ssid)
@classmethod
def load_mac_list(cls, file_path: pathlib.Path) -> List[str]:
"""
Securely load MAC address ignore list
Supports both JSON and Python list formats
"""
if not file_path.exists():
logger.warning(f"MAC ignore list not found: {file_path}")
return []
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read().strip()
# Try JSON format first
if content.startswith('[') and content.endswith(']'):
try:
mac_list = json.loads(content)
if not isinstance(mac_list, list):
raise ValueError("JSON content is not a list")
except json.JSONDecodeError:
# Fall back to Python list parsing
mac_list = cls._parse_python_list(content, 'ignore_list')
else:
# Parse Python variable assignment
mac_list = cls._parse_python_list(content, 'ignore_list')
# Validate all MAC addresses
validated_macs = []
for mac in mac_list:
if isinstance(mac, str) and cls.validate_mac_address(mac):
validated_macs.append(mac.upper()) # Normalize to uppercase
else:
logger.warning(f"Invalid MAC address skipped: {mac}")
logger.info(f"Loaded {len(validated_macs)} valid MAC addresses")
return validated_macs
except Exception as e:
logger.error(f"Error loading MAC list from {file_path}: {e}")
return []
@classmethod
def load_ssid_list(cls, file_path: pathlib.Path) -> List[str]:
"""
Securely load SSID ignore list
Supports both JSON and Python list formats
"""
if not file_path.exists():
logger.warning(f"SSID ignore list not found: {file_path}")
return []
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read().strip()
# Try JSON format first
if content.startswith('[') and content.endswith(']'):
try:
ssid_list = json.loads(content)
if not isinstance(ssid_list, list):
raise ValueError("JSON content is not a list")
except json.JSONDecodeError:
# Fall back to Python list parsing
ssid_list = cls._parse_python_list(content, 'non_alert_ssid_list')
else:
# Parse Python variable assignment
ssid_list = cls._parse_python_list(content, 'non_alert_ssid_list')
# Validate all SSIDs
validated_ssids = []
for ssid in ssid_list:
if isinstance(ssid, str) and cls.validate_ssid(ssid):
validated_ssids.append(ssid)
else:
logger.warning(f"Invalid SSID skipped: {ssid}")
logger.info(f"Loaded {len(validated_ssids)} valid SSIDs")
return validated_ssids
except Exception as e:
logger.error(f"Error loading SSID list from {file_path}: {e}")
return []
@staticmethod
def _parse_python_list(content: str, variable_name: str) -> List[str]:
"""
Safely parse Python list assignment without exec()
Only handles simple list assignments like: var_name = ['item1', 'item2']
"""
# Remove comments and extra whitespace
lines = [line.split('#')[0].strip() for line in content.split('\n')]
content_clean = ' '.join(lines)
# Look for variable assignment pattern
pattern = rf'{re.escape(variable_name)}\s*=\s*(\[.*?\])'
match = re.search(pattern, content_clean, re.DOTALL)
if not match:
raise ValueError(f"Could not find {variable_name} assignment")
list_str = match.group(1)
# Use json.loads for safe parsing (requires proper JSON format)
try:
# Replace single quotes with double quotes for JSON compatibility
json_str = list_str.replace("'", '"')
return json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"Could not parse list as JSON: {e}")
@classmethod
def save_mac_list(cls, mac_list: List[str], file_path: pathlib.Path) -> None:
"""Save MAC list in secure JSON format"""
# Validate all MACs before saving
valid_macs = [mac.upper() for mac in mac_list if cls.validate_mac_address(mac)]
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(valid_macs, f, indent=2)
logger.info(f"Saved {len(valid_macs)} MAC addresses to {file_path}")
@classmethod
def save_ssid_list(cls, ssid_list: List[str], file_path: pathlib.Path) -> None:
"""Save SSID list in secure JSON format"""
# Validate all SSIDs before saving
valid_ssids = [ssid for ssid in ssid_list if cls.validate_ssid(ssid)]
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(valid_ssids, f, indent=2)
logger.info(f"Saved {len(valid_ssids)} SSIDs to {file_path}")
def load_ignore_lists(config: dict) -> tuple[List[str], List[str]]:
"""
Convenience function to load both MAC and SSID ignore lists
Returns: (mac_list, ssid_list)
"""
loader = SecureIgnoreLoader()
# Load MAC ignore list
mac_path = pathlib.Path('./ignore_lists') / config['paths']['ignore_lists']['mac']
mac_list = loader.load_mac_list(mac_path)
# Load SSID ignore list
ssid_path = pathlib.Path('./ignore_lists') / config['paths']['ignore_lists']['ssid']
ssid_list = loader.load_ssid_list(ssid_path)
return mac_list, ssid_list