Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 135 additions & 65 deletions .env_example

Large diffs are not rendered by default.

39 changes: 36 additions & 3 deletions .pyrit_conf_example
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ operation: op_trash_panda
# - 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
# - Local files retain standard dotenv parsing and ${NAME} interpolation.
# Key Vault references in local files remain literal.
# - Interpolation follows load order: .env.local can reference .env, but .env
# cannot see variables introduced only by the later .env.local.
# - Loading is non-transactional. If a later source fails, values loaded by
# earlier sources remain in the process environment.
#
# Example:
# env_files:
Expand All @@ -96,16 +102,43 @@ operation: op_trash_panda

# Azure Key Vault Environment References
# ---------------------------------------
# List of AKV secret URLs to load during initialization.
# Each secret's value must be the full contents of a .env file.
# Loaded after env_files, so AKV secrets take precedence.
# Ordered AKV secret URLs whose values are bootstrap .env documents.
# Documents load in list order before local files and use standard ${NAME}
# interpolation. Complete values in a bootstrap document may reference a
# scalar secret in that document's vault using a full URL:
# kv:https://my-vault.vault.azure.net/secrets/SECRET_NAME
# Include a version to pin a secret:
# kv:https://my-vault.vault.azure.net/secrets/SECRET_NAME/SECRET_VERSION
# Short secret names such as kv:SECRET_NAME are rejected.
# Cross-vault child references are rejected.
# Only .vault.azure.net, .vault.azure.cn, and .vault.usgovcloudapi.net hosts
# are accepted. Arbitrary HTTPS hosts and malformed secret paths are rejected.
# Referenced secrets are not cached; each kv: occurrence performs a vault read.
# Referenced values are terminal scalars; they are not parsed for more references.
# Source precedence is AKV bootstraps -> ~/.pyrit/.env -> ~/.pyrit/.env.local.
# Explicit env_files replace the default files and load after the AKV bootstrap.
# PyRIT emits a warning when these local files coexist with env_akv_ref so stale
# configuration cannot silently mask or be mistaken for the Key Vault document.
# When migrating, remove or clear ~/.pyrit/.env and ~/.pyrit/.env.local, remove
# explicit env_files if Key Vault should be authoritative, and restart PyRIT.
# Authentication uses DefaultAzureCredential (managed identity, Azure CLI, etc.).
# Key Vault operations use up to three retries with exponential backoff and
# raise KeyVaultInitializationException on bootstrap or secret-resolution failure.
# If env_akv_ref and local files are omitted, PyRIT uses existing process
# environment variables and continues initialization.
#
# Requires: pip install azure-keyvault-secrets
#
# Example:
# env_akv_ref:
# - https://my-vault.vault.azure.net/secrets/my-pyrit-env
#
# Strict validation applies only to the Key Vault bootstrap and is enabled by
# default. Set this to false to skip malformed or valueless bootstrap entries
# with a warning while loading valid entries. Local files retain standard
# python-dotenv parsing regardless of this setting.
# Empty assignments (NAME=) and child secrets containing an empty string are valid.
# env_akv_strict: false

# Max Concurrent Scenario Runs
# ----------------------------
Expand Down
106 changes: 91 additions & 15 deletions doc/getting_started/pyrit_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,35 @@ When PyRIT initializes, environment variables are loaded in a specific order. **

```{mermaid}
flowchart LR
A["1. System Environment"] --> B{"env_files in .pyrit_conf?"}
B -->|No| C["2. ~/.pyrit/.env"]
C --> D["3. ~/.pyrit/.env.local"]
B -->|Yes| E["2. Your specified files (in order)"]
A["System environment"] --> B{"env_akv_ref configured?"}
B -->|Yes| C["AKV bootstrap documents in order"]
B -->|No| D{"Explicit env_files?"}
C --> D
D -->|Yes| E["Explicit files in order"]
D -->|No| F["~/.pyrit/.env"]
F --> G["~/.pyrit/.env.local"]
```

**Default behavior** (no `env_files` field in `.pyrit_conf`):
System environment variables are always the baseline. If no AKV bootstrap document or environment file is available, PyRIT continues initialization using the existing process environment only.

**Default file behavior** (no `env_akv_ref` or `env_files` field in `.pyrit_conf`):

| Priority | Source | Description |
|----------|--------|-------------|
| ---------- | -------- | ------------- |
| Lowest | System environment variables | Always loaded as the baseline |
| Medium | `~/.pyrit/.env` | Default config file (loaded if it exists) |
| Highest | `~/.pyrit/.env.local` | Local overrides (loaded if it exists) |

**Custom behavior** (with `env_files` field): Only your specified files are loaded, in order. Default paths are completely ignored.
**AKV behavior** (with `env_akv_ref`): The referenced secrets load in list order before local files. Unless custom `env_files` are configured, `~/.pyrit/.env` loads afterward and `~/.pyrit/.env.local` loads last. Later bootstrap documents and local files may override earlier values.

PyRIT emits a warning when `env_akv_ref` is selected and default or explicit environment files coexist with it. The warning distinguishes files that are ignored from files that load afterward and override Key Vault values, making stale migration files visible at startup. When migrating to Key Vault, clear or remove `~/.pyrit/.env` and `~/.pyrit/.env.local`, remove explicit `env_files` when Key Vault should be the only source, and re-initialize PyRIT so values already present in the process environment cannot mask the Key Vault configuration.

**Custom behavior** (with `env_files` field): Only your specified files are loaded, in order. They override Key Vault bootstrap values when both fields are configured, and default paths are completely ignored.

### Using .env.local for Overrides

You can use `~/.pyrit/.env.local` to override values in `~/.pyrit/.env` without modifying the base file. This is useful for:

- Testing different targets
- Using personal credentials instead of shared ones
- Switching between configurations quickly
Expand Down Expand Up @@ -107,7 +117,7 @@ Use `pyrit list initializers` in the CLI to see all registered initializers. See
Most users should enable the following initializers. These are what the `.pyrit_conf_example` ships with and are required for features like `pyrit_scan` and automated scenarios.

| Initializer | What It Registers | When You Need It |
|---|---|---|
| --- | --- | --- |
| `target` | Prompt targets (OpenAI, Azure, AML, etc.) into the `TargetRegistry` | **Required for `pyrit_scan`** and any registry-based workflows |
| `scorer` | Scorers (refusal, content safety, harm-category, Likert, etc.) into the `ScorerRegistry` | **Required for automated scoring** and `pyrit_scan` evaluations |
| `technique` | Attack techniques into the `AttackTechniqueRegistry` | **Required for `pyrit_scan` scenarios** that select techniques |
Expand Down Expand Up @@ -159,18 +169,79 @@ initialization_scripts:

Environment file paths to load during initialization. Later files override values from earlier files.

| Value | Behavior |
| ----------------- | -------------------------------------------------------------------- |
| Omitted or `null` | Load default `~/.pyrit/.env` and `~/.pyrit/.env.local` if they exist |
| `[]` (empty list) | Load **no** environment files |
| List of paths | Load **only** the specified files (defaults are skipped) |
| Value | Behavior |
| ----------------- | -------------------------------------------------------- |
| Omitted or `null` | Load default `~/.pyrit/.env` and `~/.pyrit/.env.local` |
| `[]` (empty list) | Load **no** environment files |
| List of paths | Load **only** the specified files (defaults are skipped) |

```yaml
env_files:
- /path/to/.env
- /path/to/.env.local
```

Local environment files use standard python-dotenv parsing and `${NAME}` interpolation. Interpolation follows assignment and file load order. The default `.env.local` can reference a value loaded earlier from `.env`, for example `FOOBAR=${OPENAI_CHAT_ENDPOINT}`. A `.env` value cannot reference a variable introduced only by the later `.env.local`; values are not resolved retroactively. Explicit `env_files` follow the order in which they are listed.

`env_akv_strict` does not apply to local files: malformed local lines retain python-dotenv's existing permissive skip-and-warn behavior. Local `kv:`, `akv:`, `azure_key_vault:`, and `env_akv_ref:` values remain literal; child-secret resolution is limited to Key Vault bootstrap documents. PyRIT does not define `env:` or `literal:` interpolation syntax. Use standard `${NAME}` interpolation instead.

Environment loading preserves the historical non-transactional dotenv behavior. Each bootstrap document and local file updates `os.environ` as it loads. If a later source or child-secret lookup fails, assignments made by earlier sources remain in the process environment.

When `env_akv_ref` is not configured, an empty `env_files` list or missing default files leaves existing process environment variables unchanged and initialization continues.

### `env_akv_ref`

Ordered Azure Key Vault secret URLs used to obtain bootstrap environment documents. Each secret value must contain dotenv-formatted entries. Authentication uses `DefaultAzureCredential`.

```yaml
env_akv_ref:
- https://my-vault.vault.azure.net/secrets/shared-pyrit-env
- https://my-vault.vault.azure.net/secrets/team-pyrit-env
```

Bootstrap documents load in list order with `override=True`; local environment files load afterward. Each document uses native dotenv interpolation against the process environment and assignments already parsed. A bootstrap document can mix literal values, `${NAME}` interpolation, and complete-value references to scalar secrets in the same vault:

```dotenv
OPENAI_CHAT_ENDPOINT="https://example.openai.azure.com/openai/v1"
OPENAI_CHAT_KEY="kv:https://my-vault.vault.azure.net/secrets/openai-chat-key"
PINNED_OPENAI_CHAT_KEY="kv:https://my-vault.vault.azure.net/secrets/openai-chat-key/version-id"
OPENAI_CHAT_MODEL="${PYRIT_OPENAI_CHAT_MODEL}"
```

Resolution is limited to one child-secret lookup:

1. PyRIT validates and loads the bootstrap dotenv document.
2. For each complete-value Key Vault reference in that document, PyRIT fetches the same-vault scalar secret and replaces the environment value.

For example, if `OPENAI_CHAT_KEY="kv:https://my-vault.vault.azure.net/secrets/openai-chat-key"`, the value of the `openai-chat-key` secret becomes `OPENAI_CHAT_KEY` verbatim. If that secret happens to contain `kv:another-secret`, the final environment value is the string `kv:another-secret`; PyRIT does not fetch `another-secret`.

References must occupy the entire value. `kv:` is the canonical Key Vault prefix; `akv:`, `azure_key_vault:`, and `env_akv_ref:` are accepted aliases.

A Key Vault reference must use a full HTTPS secret URL from the bootstrap document's vault. Supported vault DNS suffixes are `.vault.azure.net`, `.vault.azure.cn`, and `.vault.usgovcloudapi.net`. An unversioned URL reads the latest secret version at initialization. Include the version in the URL to pin it. Short names, malformed paths, arbitrary hosts, and cross-vault child references are rejected before a client is created.

PyRIT does not cache referenced secrets. Each `kv:` occurrence in a bootstrap document performs a Key Vault read during initialization, including repeated references to the same URI. A later bootstrap or local file may override a reference after it has already been fetched.

```dotenv
LATEST_KEY_URI="kv:https://my-vault.vault.azure.net/secrets/openai-chat-key"
PINNED_KEY="kv:https://my-vault.vault.azure.net/secrets/openai-chat-key/version-id"
```

The bootstrap documents are held in memory and never written to disk. They load before explicit `env_files` or the default `~/.pyrit/.env` and `~/.pyrit/.env.local`, allowing local values to override shared configuration.

### `env_akv_strict`

Controls validation only of the Key Vault bootstrap document and defaults to `true`. It does not change parsing of `.env`, `.env.local`, or explicit `env_files`.

```yaml
env_akv_strict: false
```

In strict mode, any malformed dotenv line or variable without an equals sign stops that bootstrap document before it mutates the environment. Empty assignments such as `OPTIONAL_VALUE=` remain valid and set the variable to an empty string. A referenced Key Vault secret whose value is an empty string is also valid. A missing value represented by `None` is treated as an error. With `env_akv_strict: false`, PyRIT emits a warning containing only malformed line numbers and valueless variable names, skips those entries, and loads the valid assignments. Secret values are never included in the warning.

Non-strict mode does not suppress Key Vault or reference failures. Missing secrets, invalid `kv:` URLs, and bootstrap documents with no valid assignments still stop initialization. Because loading is non-transactional, values from earlier bootstrap documents remain if a later document fails, and raw values from the current document may remain if a child-secret lookup fails.

Key Vault clients use an explicit Azure retry policy with up to three retries and exponential backoff. Bootstrap parsing, invalid or missing secrets, authentication, authorization, and Azure transport failures are raised as `KeyVaultInitializationException` with the original exception preserved as the cause. The exception remains `ValueError`-compatible for callers migrating from the previous contract.

### `silent`

If `true`, suppresses print statements during initialization. Useful for non-interactive environments or when embedding PyRIT in other tools. Defaults to `false`.
Expand All @@ -180,7 +251,7 @@ If `true`, suppresses print statements during initialization. Useful for non-int
Client settings for connecting to or launching a PyRIT backend.

| Field | Description | Default |
|---|---|---|
| --- | --- | --- |
| `url` | Backend URL used when `--server-url` is omitted | `http://localhost:8000` |
| `startup_timeout` | Seconds `pyrit_scan --start-server` waits for a healthy backend before terminating the spawned process | `120` |

Expand Down Expand Up @@ -216,7 +287,7 @@ This means you can set sensible defaults in `~/.pyrit/.pyrit_conf` and override

The 3-layer model above determines **which config values are selected**. Once resolved, the values are applied in a fixed runtime order:

1. Environment files are loaded
1. Configured AKV bootstrap documents load in order, followed by selected environment files
2. Default values are reset
3. Memory database is configured (from `memory_db_type`)
4. Initializers are executed in listed order
Expand Down Expand Up @@ -299,6 +370,11 @@ initializers:
# - /path/to/.env
# - /path/to/.env.local

# Optional ordered Azure Key Vault bootstrap environment documents
# env_akv_ref:
# - https://my-vault.vault.azure.net/secrets/my-pyrit-env
# env_akv_strict: false # Optional; defaults to true

# Suppress initialization messages
silent: false

Expand Down
2 changes: 2 additions & 0 deletions pyrit/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
EmptyResponseException,
ExperimentalWarning,
InvalidJsonException,
KeyVaultInitializationException,
MissingPromptPlaceholderException,
PyritException,
RateLimitException,
Expand Down Expand Up @@ -53,6 +54,7 @@
"get_retry_max_num_attempts",
"handle_bad_request_exception",
"InvalidJsonException",
"KeyVaultInitializationException",
"MissingPromptPlaceholderException",
"PyritException",
"pyrit_custom_result_retry",
Expand Down
19 changes: 19 additions & 0 deletions pyrit/exceptions/exception_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,25 @@ def __init__(self, *, status_code: int = 500, message: str = "Server Error", bod
self.body = body


class KeyVaultInitializationException(PyritException, ValueError): # noqa: N818
"""Exception raised when Key Vault-backed environment initialization fails."""

def __init__(
self,
*,
status_code: int = 500,
message: str = "Key Vault environment initialization failed",
) -> None:
"""
Initialize a Key Vault initialization exception.

Args:
status_code (int): HTTP-style status code associated with the failure.
message (str): Human-readable failure description.
"""
super().__init__(status_code=status_code, message=message)


class EmptyResponseException(BadRequestException):
"""Exception class for empty response errors."""

Expand Down
Loading
Loading