from functools import lru_cache import tomli from agentic_security.logutils import logger SETTINGS_VERSION = 2 @lru_cache(maxsize=1) def settings_var(name: str, default=None): return get_or_create_config().get_config_value(name, default) @lru_cache(maxsize=1) def get_or_create_config(): cfg = SettingsMixin() cfg.get_or_create_config() return cfg class SettingsMixin: config = {} default_path = "agentic_security.toml" def get_or_create_config(self) -> bool: if not self.has_local_config(): self.generate_default_settings() return False self.load_config(self.default_path) settings_version = self.get_config_value("general.version") if settings_version and settings_version != SETTINGS_VERSION: logger.error( f"Configuration version mismatch: expected {SETTINGS_VERSION}, got {settings_version}." ) return False return True def has_local_config(self): try: with open(self.default_path): return True except FileNotFoundError: return False @classmethod def load_config(cls, config_path: str): """ Load configuration from a TOML file and store it in the class variable. Args: config_path (str): Path to the TOML configuration file. Raises: FileNotFoundError: If the configuration file is not found. toml.TomlDecodeError: If the configuration file has syntax errors. """ try: with open(config_path, "rb") as config_file: cls.config = tomli.load(config_file) logger.info(f"Configuration loaded successfully from {config_path}.") except FileNotFoundError: logger.error(f"Configuration file {config_path} not found.") raise except Exception as e: logger.error(f"Error parsing TOML configuration: {e}") raise @classmethod def get_config_value(cls, key: str, default=None): """ Retrieve a configuration value by key from the loaded configuration. Args: key (str): Dot-separated key path to the configuration value (e.g., 'general.maxBudget'). default: Default value if the key is not found. Returns: The configuration value if found, otherwise the default value. """ keys = key.split(".") value = cls.config for k in keys: if isinstance(value, dict) and k in value: value = value[k] else: return default return value def generate_default_settings(self, host: str = "0.0.0.0", port: int = 8718): # Accept host / port as parameters with open(self.default_path, "w") as f: f.write( """ [general] # General configuration for the security scan llmSpec = \""" POST http://$HOST:$PORT/v1/self-probe Authorization: Bearer XXXXX Content-Type: application/json { "prompt": "<>" } \""" # LLM API specification maxBudget = 1000000 # Maximum budget for the scan max_th = 0.3 # Maximum failure threshold (percentage) optimize = false # Enable optimization during scanning enableMultiStepAttack = false # Enable multi-step attack simulations version = $SETTINGS_VERSION # [modules.LLM-Jailbreak-Classifier] # dataset_name = "markush1/LLM-Jailbreak-Classifier" [modules.aya-23-8B_advbench_jailbreak] dataset_name = "simonycl/aya-23-8B_advbench_jailbreak" [modules.AgenticBackend] dataset_name = "AgenticBackend" [modules.AgenticBackend.opts] port = $PORT modules = ["encoding"] [thresholds] # Threshold settings low = 0.15 medium = 0.3 high = 0.5 [secrets] # Secrets for the security scan from environment variables OPENAI_API_KEY = "$OPENAI_API_KEY" DEEPSEEK_API_KEY = "$DEEPSEEK_API_KEY" [caching] enable = true cache_size = 10000 use_disk_cache = false [network] retry = 3 timeout_connect = 30 timeout_response = 90 [fuzzer] max_prompt_lenght = 2048 budget_multiplier = 100000000 initial_optimizer_points = 25 min_failure_samples = 5 failure_rate_threshold = 0.5 """.replace( "$HOST", host ) .replace("$PORT", str(port)) .replace("$SETTINGS_VERSION", str(SETTINGS_VERSION)) ) logger.info( f"Default configuration generated successfully to {self.default_path}." )