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 configuration

  • sandroid-config show - Display current configuration

  • sandroid-config validate - Validate configuration files

  • sandroid-config set - Modify configuration values

  • sandroid-config get - Retrieve configuration values

  • sandroid-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 configuration

  • All 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):

  1. Command-line arguments

  2. Environment variables (SANDROID_*)

  3. Configuration file (specified with --config)

  4. 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)

  5. 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

  1. Use TOML format for human-readable configuration files

  2. Validate configuration before running analysis

  3. Use environment variables for sensitive values (API keys)

  4. Create configuration templates for common use cases

  5. Document custom configuration sections

  6. Version configuration files for reproducibility

  7. Use XDG-compliant paths for configuration storage

  8. Handle configuration errors gracefully

  9. Provide sensible defaults for all configuration values

  10. Test configuration changes before production use

See Also