Configuration API
The Sandroid configuration system provides robust, type-safe configuration management with support for multiple file formats, environment variables, and validation.
Configuration Schema
The configuration schema defines the structure and validation rules for Sandroid configuration using Pydantic models.
Main Configuration Class:
from sandroid.config.schema import SandroidConfig
# Load configuration with validation
config = SandroidConfig.from_file("config.toml")
# Access configuration values
print(f"Log level: {config.log_level}")
print(f"Device name: {config.emulator.device_name}")
print(f"Results path: {config.paths.results_path}")
Configuration Loader
The configuration loader handles file discovery, parsing, and environment variable integration.
Key Features:
XDG Base Directory compliance
Multi-format support (TOML, YAML, JSON)
Environment variable override
Hierarchical configuration loading
Validation and error reporting
Basic Usage:
from sandroid.config.loader import load_config, find_config_file
# Load configuration from default locations
config = load_config()
# Load specific configuration file
config = load_config("custom-config.toml")
# Find configuration file path
config_path = find_config_file()
print(f"Using config: {config_path}")
Configuration CLI
Command-line interface for configuration management.
Available Commands:
sandroid-config init
- Create default configurationsandroid-config show
- Display current configurationsandroid-config validate
- Validate configuration filessandroid-config set
- Modify configuration valuessandroid-config get
- Retrieve configuration valuessandroid-config paths
- Show configuration file locations
Usage Examples:
# Initialize configuration
sandroid-config init
# Show current configuration
sandroid-config show
# Validate configuration
sandroid-config validate
# Set configuration value
sandroid-config set analysis.number_of_runs 5
# Get configuration value
sandroid-config get emulator.device_name
Configuration Structure
Complete Configuration Schema:
from sandroid.config.schema import SandroidConfig
# Example complete configuration
config_data = {
"log_level": "INFO",
"output_file": "analysis_results.json",
"emulator": {
"device_name": "Pixel_6_Pro_API_31",
"android_emulator_path": "~/Android/Sdk/emulator/emulator",
"avd_manager_path": "~/Android/Sdk/cmdline-tools/latest/bin/avdmanager"
},
"frida": {
"server_version": "latest",
"install_path": "/data/local/tmp/",
"timeout": 30
},
"network": {
"capture_enabled": False,
"proxy_host": None,
"proxy_port": None,
"interface": "eth0"
},
"paths": {
"results_path": "./results/",
"raw_results_path": "./results/raw/",
"temp_path": "/tmp/sandroid/",
"cache_path": "~/.cache/sandroid/",
"log_path": "~/.cache/sandroid/logs/"
},
"analysis": {
"number_of_runs": 2,
"monitor_processes": True,
"monitor_network": False,
"show_deleted_files": False,
"calculate_hashes": False,
"list_apks": False,
"avoid_noise_filter": False
},
"trigdroid": {
"enabled": False,
"timeout": 300,
"trigger_sets": ["network", "filesystem", "permissions"]
},
"ai": {
"enabled": False,
"provider": "google",
"model": "gemini-pro",
"api_key": None,
"temperature": 0.7
},
"report": {
"generate_pdf": False,
"include_screenshots": True,
"include_ai_analysis": True,
"template": "default"
},
"features": {
"screenshot_interval": 0,
"recording_enabled": False,
"max_screenshot_size": "1920x1080"
},
"custom": {} # For user extensions
}
# Create and validate configuration
config = SandroidConfig(**config_data)
Environment Variables
Environment Variable Mapping:
All configuration values can be set via environment variables using the SANDROID_
prefix:
import os
from sandroid.config.loader import load_config
# Set environment variables
os.environ['SANDROID_LOG_LEVEL'] = 'DEBUG'
os.environ['SANDROID_EMULATOR__DEVICE_NAME'] = 'Custom_Device'
os.environ['SANDROID_ANALYSIS__NUMBER_OF_RUNS'] = '5'
os.environ['SANDROID_PATHS__RESULTS_PATH'] = '/custom/results'
# Load configuration (environment variables override file settings)
config = load_config()
print(f"Log level: {config.log_level}") # DEBUG
print(f"Device: {config.emulator.device_name}") # Custom_Device
print(f"Runs: {config.analysis.number_of_runs}") # 5
Environment Variable Format:
Use double underscores (
__
) for nested configurationAll uppercase for variable names
Prefix with
SANDROID_
# Basic configuration
export SANDROID_LOG_LEVEL=DEBUG
export SANDROID_OUTPUT_FILE=custom_results.json
# Nested configuration
export SANDROID_EMULATOR__DEVICE_NAME=Pixel_8_API_34
export SANDROID_ANALYSIS__NUMBER_OF_RUNS=3
export SANDROID_PATHS__RESULTS_PATH=/tmp/analysis
# Boolean values
export SANDROID_ANALYSIS__MONITOR_NETWORK=true
export SANDROID_REPORT__GENERATE_PDF=false
Configuration File Formats
TOML Format (Recommended):
log_level = "INFO"
output_file = "sandroid.json"
[emulator]
device_name = "Pixel_6_Pro_API_31"
android_emulator_path = "~/Android/Sdk/emulator/emulator"
[analysis]
number_of_runs = 2
monitor_processes = true
monitor_network = false
[paths]
results_path = "./results/"
cache_path = "~/.cache/sandroid/"
[ai]
enabled = false
provider = "google"
model = "gemini-pro"
YAML Format:
log_level: INFO
output_file: sandroid.json
emulator:
device_name: Pixel_6_Pro_API_31
android_emulator_path: ~/Android/Sdk/emulator/emulator
analysis:
number_of_runs: 2
monitor_processes: true
monitor_network: false
paths:
results_path: ./results/
cache_path: ~/.cache/sandroid/
JSON Format:
{
"log_level": "INFO",
"output_file": "sandroid.json",
"emulator": {
"device_name": "Pixel_6_Pro_API_31",
"android_emulator_path": "~/Android/Sdk/emulator/emulator"
},
"analysis": {
"number_of_runs": 2,
"monitor_processes": true,
"monitor_network": false
},
"paths": {
"results_path": "./results/",
"cache_path": "~/.cache/sandroid/"
}
}
Configuration Loading Priority
Configuration values are loaded in the following priority order (highest to lowest):
Command-line arguments
Environment variables (
SANDROID_*
)Configuration file (specified with
--config
)Default configuration files (in search order): -
./sandroid.toml
(current directory) -./config.toml
(current directory) -~/.config/sandroid/sandroid.toml
(user config) -/etc/sandroid/sandroid.toml
(system config)Default values (hardcoded in schema)
Search Path Example:
from sandroid.config.loader import get_config_search_paths
# Get configuration search paths
search_paths = get_config_search_paths()
for path in search_paths:
print(f"Searching: {path}")
Configuration Validation
Automatic Validation:
from sandroid.config.schema import SandroidConfig
from pydantic import ValidationError
try:
# This will validate all fields
config = SandroidConfig.from_file("config.toml")
print("Configuration is valid")
except ValidationError as e:
print("Configuration validation failed:")
for error in e.errors():
print(f"- {error['loc']}: {error['msg']}")
Manual Validation:
from sandroid.config.loader import validate_config_file
# Validate configuration file
is_valid, errors = validate_config_file("config.toml")
if is_valid:
print("✅ Configuration is valid")
else:
print("❌ Configuration validation failed:")
for error in errors:
print(f"- {error}")
Field Validation Rules:
from sandroid.config.schema import SandroidConfig
from pydantic import Field, validator
class CustomConfig(SandroidConfig):
# Add custom validation
@validator('analysis.number_of_runs')
def validate_runs(cls, v):
if v < 2:
raise ValueError('number_of_runs must be at least 2')
return v
@validator('paths.results_path')
def validate_results_path(cls, v):
if not os.path.exists(os.path.dirname(v)):
raise ValueError(f'Results directory does not exist: {v}')
return v
Dynamic Configuration
Runtime Configuration Updates:
from sandroid.config.schema import SandroidConfig
from sandroid.config.loader import load_config, save_config
# Load current configuration
config = load_config()
# Modify configuration
config.analysis.number_of_runs = 5
config.analysis.monitor_network = True
config.log_level = "DEBUG"
# Save updated configuration
save_config(config, "updated_config.toml")
# Reload to verify
updated_config = load_config("updated_config.toml")
print(f"Updated runs: {updated_config.analysis.number_of_runs}")
Configuration Templates:
from sandroid.config.schema import SandroidConfig
def create_malware_analysis_config():
"""Create configuration optimized for malware analysis"""
config = SandroidConfig()
# Malware analysis settings
config.log_level = "DEBUG"
config.analysis.number_of_runs = 3
config.analysis.monitor_network = True
config.analysis.show_deleted_files = True
config.analysis.calculate_hashes = True
config.analysis.avoid_noise_filter = True
# Enhanced monitoring
config.features.screenshot_interval = 5
config.trigdroid.enabled = True
config.trigdroid.timeout = 600
# AI analysis
config.ai.enabled = True
config.report.generate_pdf = True
return config
def create_development_config():
"""Create configuration for app development testing"""
config = SandroidConfig()
# Development settings
config.log_level = "INFO"
config.analysis.number_of_runs = 1
config.analysis.monitor_processes = False
config.features.screenshot_interval = 10
# Faster analysis
config.analysis.avoid_noise_filter = False
config.paths.results_path = "./dev_results/"
return config
# Usage
malware_config = create_malware_analysis_config()
dev_config = create_development_config()
Configuration Integration
Using Configuration in Analysis:
from sandroid.config.loader import load_config
from sandroid.core.toolbox import Toolbox
from sandroid.analysis.network import Network
from sandroid.features.screenshot import Screenshot
def run_configured_analysis():
"""Run analysis using configuration settings"""
# Load configuration
config = load_config()
# Configure toolbox
Toolbox.configure(config)
# Configure network monitoring
if config.analysis.monitor_network:
network_monitor = Network()
network_monitor.configure(config.network)
network_monitor.gather()
# Configure screenshots
if config.features.screenshot_interval > 0:
screenshot = Screenshot()
screenshot.set_interval(config.features.screenshot_interval)
screenshot.run()
# Run analysis with configured parameters
return Toolbox.run_analysis(
runs=config.analysis.number_of_runs,
monitor_processes=config.analysis.monitor_processes
)
Configuration-Aware Classes:
from sandroid.config.loader import load_config
from sandroid.config.schema import SandroidConfig
class ConfigurableAnalyzer:
def __init__(self, config: SandroidConfig = None):
self.config = config or load_config()
def run_analysis(self):
"""Run analysis using configuration settings"""
# Use configuration values
runs = self.config.analysis.number_of_runs
monitor_network = self.config.analysis.monitor_network
print(f"Running {runs} analysis iterations")
print(f"Network monitoring: {'enabled' if monitor_network else 'disabled'}")
# Implementation here...
Configuration Extensions
Custom Configuration Sections:
from sandroid.config.schema import SandroidConfig
from pydantic import BaseModel
from typing import Dict, Any
class CustomAnalysisConfig(BaseModel):
custom_timeout: int = 300
custom_flags: Dict[str, Any] = {}
special_mode: bool = False
class ExtendedSandroidConfig(SandroidConfig):
custom_analysis: CustomAnalysisConfig = CustomAnalysisConfig()
# Usage
config_data = {
"log_level": "INFO",
"custom_analysis": {
"custom_timeout": 600,
"custom_flags": {"experimental": True},
"special_mode": True
}
}
config = ExtendedSandroidConfig(**config_data)
print(f"Custom timeout: {config.custom_analysis.custom_timeout}")
Plugin Configuration:
from sandroid.config.schema import SandroidConfig
def load_plugin_config(plugin_name: str, config: SandroidConfig):
"""Load configuration for a specific plugin"""
plugin_config = config.custom.get(plugin_name, {})
# Apply plugin-specific defaults
defaults = {
'enabled': True,
'timeout': 120,
'options': {}
}
# Merge with defaults
for key, value in defaults.items():
plugin_config.setdefault(key, value)
return plugin_config
# Usage
config = load_config()
my_plugin_config = load_plugin_config('my_plugin', config)
Error Handling
Configuration Error Handling:
from sandroid.config.loader import load_config
from sandroid.config.schema import ConfigurationError
from pydantic import ValidationError
import logging
logger = logging.getLogger(__name__)
def safe_load_config(config_path=None):
"""Safely load configuration with comprehensive error handling"""
try:
config = load_config(config_path)
logger.info("Configuration loaded successfully")
return config
except FileNotFoundError as e:
logger.error(f"Configuration file not found: {e}")
logger.info("Using default configuration")
return SandroidConfig() # Return default config
except ValidationError as e:
logger.error("Configuration validation failed:")
for error in e.errors():
logger.error(f"- {'.'.join(str(x) for x in error['loc'])}: {error['msg']}")
raise ConfigurationError("Invalid configuration") from e
except Exception as e:
logger.error(f"Unexpected error loading configuration: {e}")
raise ConfigurationError("Configuration loading failed") from e
Best Practices
Use TOML format for human-readable configuration files
Validate configuration before running analysis
Use environment variables for sensitive values (API keys)
Create configuration templates for common use cases
Document custom configuration sections
Version configuration files for reproducibility
Use XDG-compliant paths for configuration storage
Handle configuration errors gracefully
Provide sensible defaults for all configuration values
Test configuration changes before production use
See Also
Core API - Core API using configuration
Analysis API - Analysis modules configuration
Features API - Feature modules configuration
Configuration - Detailed configuration guide
Installation - Configuration during installation