Skip to content

Commit 65f02b0

Browse files
author
Behnam Ousat
committed
config loader
1 parent 785213b commit 65f02b0

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

.pyrit_conf_example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ operation: op_trash_panda
125125
# env_akv_ref:
126126
# - https://my-vault.vault.azure.net/secrets/my-pyrit-env
127127

128+
# Datasets
129+
# --------
130+
# List of seed dataset names to load into memory after initialization completes.
131+
# Each name must match a registered dataset (run `pyrit_scan --list-datasets` to
132+
# see available datasets). Loaded datasets are added to CentralMemory and are
133+
# available to scenarios and attacks.
134+
#
135+
# Example:
136+
# datasets:
137+
# - airt_illegal
138+
# - airt_malware
139+
128140
# Max Concurrent Scenario Runs
129141
# ----------------------------
130142
# Maximum number of scenario runs that can execute concurrently in the backend.

pyrit/setup/configuration_loader.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class ConfigurationLoader(YamlLoadable):
103103
None means "use defaults", [] means "load nothing".
104104
env_files: List of environment file paths to load.
105105
None means "use defaults (.env, .env.local)", [] means "load nothing".
106+
datasets: List of seed dataset names to load into memory after initialization.
106107
silent: Whether to suppress initialization messages.
107108
operator: Name for the current operator, e.g. a team or username.
108109
operation: Name for the current operation.
@@ -123,6 +124,10 @@ class ConfigurationLoader(YamlLoadable):
123124
- /path/to/.env
124125
- /path/to/.env.local
125126
127+
datasets:
128+
- airt_illegal
129+
- airt_malware
130+
126131
silent: false
127132
128133
operator: my_team
@@ -141,6 +146,7 @@ class ConfigurationLoader(YamlLoadable):
141146
initialization_scripts: list[str] | None = None
142147
env_files: list[str] | None = None
143148
env_akv_ref: list[str] | None = None
149+
datasets: list[str] = field(default_factory=list)
144150
silent: bool = False
145151
operator: str | None = None
146152
operation: str | None = None
@@ -379,6 +385,7 @@ def load_with_overrides(
379385
config_data["initialization_scripts"] = default_config.initialization_scripts
380386
config_data["env_files"] = default_config.env_files
381387
config_data["env_akv_ref"] = default_config.env_akv_ref
388+
config_data["datasets"] = default_config.datasets
382389
config_data["silent"] = default_config.silent
383390
if default_config.operator:
384391
config_data["operator"] = default_config.operator
@@ -405,6 +412,7 @@ def load_with_overrides(
405412
config_data["initialization_scripts"] = explicit_config.initialization_scripts
406413
config_data["env_files"] = explicit_config.env_files
407414
config_data["env_akv_ref"] = explicit_config.env_akv_ref
415+
config_data["datasets"] = explicit_config.datasets
408416
config_data["silent"] = explicit_config.silent
409417
if explicit_config.operator:
410418
config_data["operator"] = explicit_config.operator
@@ -560,7 +568,8 @@ async def initialize_pyrit_async(self) -> None:
560568
Initialize PyRIT with the loaded configuration.
561569
562570
This method resolves all initializer names to instances and calls
563-
the core initialize_pyrit_async function.
571+
the core initialize_pyrit_async function. Any datasets configured via
572+
the ``datasets`` block are loaded into memory after initialization.
564573
565574
Raises:
566575
ValueError: If configuration is invalid or initializers cannot be resolved.
@@ -581,6 +590,37 @@ async def initialize_pyrit_async(self) -> None:
581590
silent=self.silent,
582591
)
583592

593+
await self._load_datasets_async()
594+
595+
async def _load_datasets_async(self) -> None:
596+
"""
597+
Load the configured seed datasets into memory.
598+
599+
Fetches each dataset named in the ``datasets`` block and adds its seeds
600+
to ``CentralMemory``. This runs after PyRIT initialization so that memory
601+
is available. No-op when no datasets are configured.
602+
603+
Raises:
604+
ValueError: If any configured dataset name does not exist.
605+
"""
606+
if not self.datasets:
607+
return
608+
609+
import logging
610+
611+
from pyrit.datasets import SeedDatasetProvider
612+
from pyrit.memory import CentralMemory
613+
614+
logger = logging.getLogger(__name__)
615+
logger.info("Loading %d dataset(s) from configuration...", len(self.datasets))
616+
617+
datasets = await SeedDatasetProvider.fetch_datasets_async(dataset_names=self.datasets)
618+
619+
memory = CentralMemory.get_memory_instance()
620+
await memory.add_seed_datasets_to_memory_async(datasets=datasets, added_by="ConfigurationLoader")
621+
622+
logger.info("Successfully loaded %d dataset(s) into memory", len(datasets))
623+
584624

585625
async def initialize_from_config_async(
586626
config_path: str | pathlib.Path | None = None,

tests/unit/setup/test_configuration_loader.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,43 @@ async def test_initialize_pyrit_async_unknown_initializer_raises_error(self, moc
397397
with pytest.raises(ValueError, match="not found in registry"):
398398
await config.initialize_pyrit_async()
399399

400+
@mock.patch("pyrit.memory.CentralMemory")
401+
@mock.patch("pyrit.datasets.SeedDatasetProvider")
402+
@mock.patch("pyrit.setup.configuration_loader.initialize_pyrit_async")
403+
async def test_initialize_pyrit_async_loads_datasets(self, mock_init, mock_provider, mock_memory_cls):
404+
"""Test that configured datasets are fetched and added to memory after init."""
405+
fetched = [mock.MagicMock()]
406+
mock_provider.fetch_datasets_async = mock.AsyncMock(return_value=fetched)
407+
mock_memory = mock.MagicMock()
408+
mock_memory.add_seed_datasets_to_memory_async = mock.AsyncMock()
409+
mock_memory_cls.get_memory_instance.return_value = mock_memory
410+
411+
config = ConfigurationLoader(
412+
memory_db_type="in_memory",
413+
datasets=["airt_illegal", "airt_malware"],
414+
)
415+
await config.initialize_pyrit_async()
416+
417+
mock_init.assert_called_once()
418+
mock_provider.fetch_datasets_async.assert_awaited_once_with(
419+
dataset_names=["airt_illegal", "airt_malware"]
420+
)
421+
mock_memory.add_seed_datasets_to_memory_async.assert_awaited_once_with(
422+
datasets=fetched, added_by="ConfigurationLoader"
423+
)
424+
425+
@mock.patch("pyrit.datasets.SeedDatasetProvider")
426+
@mock.patch("pyrit.setup.configuration_loader.initialize_pyrit_async")
427+
async def test_initialize_pyrit_async_no_datasets_skips_loading(self, mock_init, mock_provider):
428+
"""Test that dataset loading is skipped when no datasets are configured."""
429+
mock_provider.fetch_datasets_async = mock.AsyncMock()
430+
431+
config = ConfigurationLoader(memory_db_type="in_memory")
432+
await config.initialize_pyrit_async()
433+
434+
mock_init.assert_called_once()
435+
mock_provider.fetch_datasets_async.assert_not_called()
436+
400437

401438
@pytest.mark.usefixtures("patch_central_database")
402439
class TestInitializeFromConfigAsync:

0 commit comments

Comments
 (0)