Garak config loads from four layers—plugin defaults, base config, site config, and CLI—with each higher layer overriding the lower; _config.py manages XDG-based file discovery, deep merging, and legacy key mapping. The config system detects sensitive values like API keys in files and warns about unsafe POSIX permissions (or recommends caution on Windows), while locking plugin configs to dicts after finalization to prevent accidental key creation.
Config file support — covering a core config, site config, and CLI config — was introduced in v0.9.0.9, allowing all three layers to set system, run, and plugin parameters.[1] garak moved to XDG paths for configuration data and caching in v0.9.0.15.[2] In v0.14.0, garak gained support for JSON config files in addition to YAML.[3]
The config priority order in garak/_config.py is, from lowest to highest: plugin code < base config < site config < run config < CLI params.[4]
garak/_config.py uses XDG base directories (xdg_config_home, xdg_data_home, xdg_cache_home) to locate user config, data, and cache under a garak subdirectory; all three directories are created at import time with mode=0o740.[4] TransientConfig.package_dir is set to the directory containing _config.py itself (pathlib.Path(__file__).parents[0]), i.e., the garak/ package root.[4]
Config files support both JSON and YAML format: JSON is tried first, and YAML is attempted as a fallback if JSON parsing fails; if both fail, a ValueError is raised.[4] _load_config_files() tracks already-loaded config files in the global config_files list and skips duplicates to prevent double-loading.[4] _combine_into() performs a deep merge of config dicts: scalar values from the incoming dict overwrite existing values, while nested dicts are recursively merged rather than replaced.[4] After config is finalized, _lock_config_as_dict() converts all nested defaultdict plugin config objects (for probes, generators, buffs, detectors, and harnesses) to plain dicts, preventing accidental key creation via attribute access.[4]
When an api_key is detected in a config file on non-Windows systems, garak/_config.py checks POSIX file permissions and emits a warning to both the log and stdout if the file is readable by group or other users.[4] On Windows, detecting api_key in a config file emits a warning recommending that sensitive values be removed or the file made owner-readable only, but does NOT check file permissions because Windows lacks POSIX permission bits.[4]
The deprecated config keys plugins.model_type and plugins.model_name (deprecated since v0.13.1.pre1) are automatically mapped to plugins.target_type and plugins.target_name respectively when loading config files, and a deprecation notice is emitted.[4] The deprecated keys plugins.probe_spec, plugins.buff_spec, and run.probe_tags (deprecated since v0.15.1.pre1) are mapped to run.spec via _map_legacy_selection(); if run.spec is already explicitly set, the legacy keys are ignored entirely — see Spec grammar and parsing for how run.spec is resolved.[4] Inside _map_legacy_selection(), empty strings and the value "auto" are treated as vacuous (unspecified) and neither trigger deprecation warnings nor set run.spec; the string "none" is not vacuous — it is an explicit empty selection that maps to probes.none, mirroring --probes none on the CLI.[4]
run.spec = None at module level in garak/_config.py means no spec was explicitly set; at resolve time this falls back to probes.* (all probes).[4] reporting.taxonomy is initialized to None at module level so that report_digest can be called directly without a full config load.[4]
Sources