-
Notifications
You must be signed in to change notification settings - Fork 661
FEAT: PyRIT Config #1343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
FEAT: PyRIT Config #1343
Changes from all commits
59d4e86
6518e39
f012424
785bbc6
1753576
2a3a79d
70df49d
3d1ff3e
930f7ec
2fdd416
cf6012d
761e94e
9c2b6cc
3e05974
dbb2e3f
3284e19
9f775a8
5a48b78
ecc4ab0
c183e34
0f638bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,21 @@ set -e | |
|
|
||
| MYPY_CACHE="/workspace/.mypy_cache" | ||
| VIRTUAL_ENV="/opt/venv" | ||
| PYRIT_CONFIG_DIR="/home/vscode/.pyrit" | ||
| PYRIT_CONFIG_FILE="$PYRIT_CONFIG_DIR/.pyrit_conf" | ||
|
|
||
| # Create the .pyrit config directory and copy example config if not exists | ||
| if [ ! -d "$PYRIT_CONFIG_DIR" ]; then | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it might be nice to have someone else weigh in on this. I'm hesitant about auto copying things over like this though. @romanlutz who is working on docker things
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused. What I would expect to happen is that my directory at What is the copying logic meant to copy? The example file? What would that be useful for?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now it's also copying over the example if the file doesn't exist which probably isn't useful like you mention. Would it make sense to copy over
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the most intuitive way to handle discovery and population of the config file in the Docker image? I'm not sure I understand Roman's original request to make sure the file is in the devcontainer, since if the example file is in the git repo then when the Docker image clones and builds it should be included. Is there something I'm missing?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we have two main options:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example file is with the code but not in home directory / .pyrit. That is where it looks for env files, not in the repo. That's why the copying over to .pyrit is odd IMO |
||
| echo "Creating PyRIT config directory..." | ||
| mkdir -p "$PYRIT_CONFIG_DIR" | ||
| fi | ||
|
|
||
| if [ ! -f "$PYRIT_CONFIG_FILE" ] && [ -f "/workspace/.pyrit_conf_example" ]; then | ||
| echo "Copying example PyRIT config file..." | ||
| cp /workspace/.pyrit_conf_example "$PYRIT_CONFIG_FILE" | ||
| echo "✅ Created $PYRIT_CONFIG_FILE from example. Edit as needed." | ||
| fi | ||
|
|
||
| # Create the mypy cache directory if it doesn't exist | ||
| if [ ! -d "$MYPY_CACHE" ]; then | ||
| echo "Creating mypy cache directory..." | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # PyRIT Configuration File Example | ||
| # ================================ | ||
| # This is a YAML-formatted configuration file. Copy to ~/.pyrit/.pyrit_conf | ||
| # or specify a custom path when loading via --config-file. | ||
| # | ||
| # For documentation on configuration options, see: | ||
| # https://github.com/Azure/PyRIT/blob/main/doc/setup/configuration.md | ||
|
|
||
| # Memory Database Type | ||
| # -------------------- | ||
| # Specifies which database backend to use for storing prompts and results. | ||
| # Options: in_memory, sqlite, azure_sql (case-insensitive) | ||
| # - in_memory: Temporary in-memory database (data lost on exit) | ||
| # - sqlite: Persistent local SQLite database (default) | ||
| # - azure_sql: Azure SQL database (requires connection string in env vars) | ||
| memory_db_type: sqlite | ||
|
|
||
| # Initializers | ||
| # ------------ | ||
| # List of built-in initializers to run during PyRIT initialization. | ||
| # Initializers configure default values for converters, scorers, and targets. | ||
| # Names are normalized to snake_case (e.g., "SimpleInitializer" -> "simple"). | ||
| # | ||
| # Available initializers: | ||
| # - simple: Basic OpenAI configuration (requires OPENAI_CHAT_* env vars) | ||
| # - airt: AI Red Team setup with Azure OpenAI (requires AZURE_OPENAI_* env vars) | ||
| # - load_default_datasets: Loads default datasets for all registered scenarios | ||
| # - objective_list: Sets default objectives for scenarios | ||
| # - openai_objective_target: Sets up OpenAI target for scenarios | ||
| # | ||
| # Each initializer can be specified as: | ||
| # - A simple string (name only) | ||
| # - A dictionary with 'name' and optional 'args' for constructor arguments | ||
| # | ||
| # Example: | ||
| # initializers: | ||
| # - simple | ||
| # - name: airt | ||
| # args: | ||
| # some_param: value | ||
| initializers: | ||
| - simple | ||
|
|
||
| # Initialization Scripts | ||
| # ---------------------- | ||
| # List of paths to custom Python scripts containing PyRITInitializer subclasses. | ||
| # Paths can be absolute or relative to the current working directory. | ||
| # | ||
| # Behavior: | ||
| # - Omit this field (or set to null): No custom scripts loaded (default) | ||
| # - Set to []: Explicitly load no scripts (same as omitting) | ||
| # - Set to list of paths: Load the specified scripts | ||
| # | ||
| # Example: | ||
| # initialization_scripts: | ||
| # - /path/to/my_custom_initializer.py | ||
| # - ./local_initializer.py | ||
|
|
||
| # Environment Files | ||
| # ----------------- | ||
| # List of .env file paths to load during initialization. | ||
| # Later files override values from earlier files. | ||
| # | ||
| # Behavior: | ||
| # - Omit this field (or set to null): Load default .env and .env.local from ~/.pyrit/ if they exist | ||
| # - Set to []: Explicitly load NO environment files | ||
| # - Set to list of paths: Load only the specified files | ||
| # | ||
| # Example: | ||
| # env_files: | ||
| # - /path/to/.env | ||
| # - /path/to/.env.local | ||
|
|
||
| # Silent Mode | ||
| # ----------- | ||
| # If true, suppresses print statements during initialization. | ||
| # Useful for non-interactive environments or when embedding PyRIT in other tools. | ||
| silent: false |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,9 @@ | |
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence | ||
|
|
||
| from pyrit.setup import ConfigurationLoader | ||
| from pyrit.setup.configuration_loader import _MEMORY_DB_TYPE_MAP | ||
|
|
||
| try: | ||
| import termcolor | ||
|
|
||
|
|
@@ -66,39 +69,74 @@ class FrontendCore: | |
| def __init__( | ||
| self, | ||
| *, | ||
| database: str = SQLITE, | ||
| config_file: Optional[Path] = None, | ||
| database: Optional[str] = None, | ||
| initialization_scripts: Optional[list[Path]] = None, | ||
| initializer_names: Optional[list[str]] = None, | ||
| env_files: Optional[list[Path]] = None, | ||
| log_level: str = "WARNING", | ||
| log_level: Optional[int] = None, | ||
| ): | ||
| """ | ||
| Initialize PyRIT context. | ||
|
|
||
| Configuration is loaded in the following order (later values override earlier): | ||
| 1. Default config file (~/.pyrit/.pyrit_conf) if it exists | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure to update devcontainer configuration to get that file into the devcontainer (if it isn't already).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be by default, but an explicit check has been added to the devcontainer shell script, as well as explicit creation of |
||
| 2. Explicit config_file argument if provided | ||
| 3. Individual CLI arguments (database, initializers, etc.) | ||
|
|
||
| Args: | ||
| config_file: Optional path to a YAML-formatted configuration file. | ||
| The file uses .pyrit_conf extension but is YAML format. | ||
| database: Database type (InMemory, SQLite, or AzureSQL). | ||
| initialization_scripts: Optional list of initialization script paths. | ||
| initializer_names: Optional list of built-in initializer names to run. | ||
| env_files: Optional list of environment file paths to load in order. | ||
| log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). Defaults to WARNING. | ||
| log_level: Logging level constant (e.g., logging.WARNING). Defaults to logging.WARNING. | ||
|
|
||
| Raises: | ||
| ValueError: If database or log_level are invalid. | ||
| ValueError: If database is invalid, or if config file is invalid. | ||
| FileNotFoundError: If an explicitly specified config_file does not exist. | ||
| """ | ||
| # Validate inputs | ||
| self._database = validate_database(database=database) | ||
| self._initialization_scripts = initialization_scripts | ||
| self._initializer_names = initializer_names | ||
| self._env_files = env_files | ||
| self._log_level = validate_log_level(log_level=log_level) | ||
| # Use provided log level or default to WARNING | ||
| self._log_level = log_level if log_level is not None else logging.WARNING | ||
|
|
||
| # Load configuration using ConfigurationLoader.load_with_overrides | ||
| try: | ||
| config = ConfigurationLoader.load_with_overrides( | ||
| config_file=config_file, | ||
| memory_db_type=database, | ||
| initializers=initializer_names, | ||
| initialization_scripts=[str(p) for p in initialization_scripts] if initialization_scripts else None, | ||
| env_files=[str(p) for p in env_files] if env_files else None, | ||
| ) | ||
| except ValueError as e: | ||
| # Re-raise with user-friendly message for CLI users | ||
| error_msg = str(e) | ||
| if "memory_db_type" in error_msg: | ||
| raise ValueError( | ||
| f"Invalid database type '{database}'. Must be one of: InMemory, SQLite, AzureSQL" | ||
| ) from e | ||
| raise | ||
|
|
||
| # Store the merged configuration | ||
| self._config = config | ||
|
|
||
| # Extract values from config for internal use | ||
| # Use canonical mapping from configuration_loader | ||
| self._database = _MEMORY_DB_TYPE_MAP[config.memory_db_type] | ||
| self._initialization_scripts = config._resolve_initialization_scripts() | ||
| self._initializer_names = ( | ||
| [ic.name for ic in config._initializer_configs] if config._initializer_configs else None | ||
| ) | ||
| self._env_files = config._resolve_env_files() | ||
|
|
||
| # Lazy-loaded registries | ||
| self._scenario_registry: Optional[ScenarioRegistry] = None | ||
| self._initializer_registry: Optional[InitializerRegistry] = None | ||
| self._initialized = False | ||
|
|
||
| # Configure logging | ||
| logging.basicConfig(level=getattr(logging, self._log_level)) | ||
| logging.basicConfig(level=self._log_level) | ||
|
|
||
| async def initialize_async(self) -> None: | ||
| """Initialize PyRIT and load registries (heavy operation).""" | ||
|
|
@@ -462,15 +500,15 @@ def validate_database(*, database: str) -> str: | |
| return database | ||
|
|
||
|
|
||
| def validate_log_level(*, log_level: str) -> str: | ||
| def validate_log_level(*, log_level: str) -> int: | ||
| """ | ||
| Validate log level. | ||
| Validate log level and convert to logging constant. | ||
|
|
||
| Args: | ||
| log_level: Log level string (case-insensitive). | ||
|
|
||
| Returns: | ||
| Validated log level in uppercase. | ||
| Validated log level as logging constant (e.g., logging.WARNING). | ||
|
|
||
| Raises: | ||
| ValueError: If log level is invalid. | ||
|
|
@@ -479,7 +517,8 @@ def validate_log_level(*, log_level: str) -> str: | |
| level_upper = log_level.upper() | ||
| if level_upper not in valid_levels: | ||
| raise ValueError(f"Invalid log level: {log_level}. Must be one of: {', '.join(valid_levels)}") | ||
| return level_upper | ||
| level_value: int = getattr(logging, level_upper) | ||
| return level_value | ||
|
|
||
|
|
||
| def validate_integer(value: str, *, name: str = "value", min_value: Optional[int] = None) -> int: | ||
|
|
@@ -734,6 +773,11 @@ async def print_initializers_list_async(*, context: FrontendCore, discovery_path | |
|
|
||
| # Shared argument help text | ||
| ARG_HELP = { | ||
| "config_file": ( | ||
| "Path to a YAML configuration file. Allows specifying database, initializers (with args), " | ||
| "initialization scripts, and env files. CLI arguments override config file values. " | ||
| "If not specified, ~/.pyrit/.pyrit_conf is loaded if it exists." | ||
| ), | ||
| "initializers": "Built-in initializer names to run before the scenario (e.g., openai_objective_target)", | ||
| "initialization_scripts": "Paths to custom Python initialization scripts to run before the scenario", | ||
| "env_files": "Paths to environment files to load in order (e.g., .env.production .env.local). Later files " | ||
|
|
@@ -768,7 +812,7 @@ def parse_run_arguments(*, args_string: str) -> dict[str, Any]: | |
| - max_retries: Optional[int] | ||
| - memory_labels: Optional[dict[str, str]] | ||
| - database: Optional[str] | ||
| - log_level: Optional[str] | ||
| - log_level: Optional[int] | ||
| - dataset_names: Optional[list[str]] | ||
| - max_dataset_size: Optional[int] | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be ~/.pyrit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the devcontainer that is the home directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but I guess wondering if using ~ is more generic for diff containers.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my experience this tends to fail given that
~can alias to home for therootuser during the build process. But I don't know enough about our Docker build pipeline to confirm it