Skip to content

Commit cb28046

Browse files
authored
Merge branch 'main' into bjagdagdorj/converter_mixed_media_type
2 parents 8eff901 + 63d4427 commit cb28046

197 files changed

Lines changed: 14051 additions & 2949 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env_example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ ADVERSARIAL_CHAT_ENDPOINT="https://xxxxx.openai.azure.com/openai/v1"
7979
ADVERSARIAL_CHAT_KEY="xxxxx"
8080
ADVERSARIAL_CHAT_MODEL="deployment-name"
8181

82+
# Objective Scorer chat target (used in scorers in scenarios)
83+
OBJECTIVE_SCORER_CHAT_ENDPOINT="https://xxxxx.openai.azure.com/openai/v1"
84+
OBJECTIVE_SCORER_CHAT_KEY="xxxxx"
85+
OBJECTIVE_SCORER_CHAT_MODEL="deployment-name"
86+
8287
AZURE_FOUNDRY_DEEPSEEK_ENDPOINT="https://xxxxx.eastus2.models.ai.azure.com"
8388
AZURE_FOUNDRY_DEEPSEEK_KEY="xxxxx"
8489
AZURE_FOUNDRY_DEEPSEEK_MODEL=""

.github/instructions/scenarios.instructions.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ Scenarios orchestrate multi-attack security testing campaigns. Each scenario gro
1111
All scenarios inherit from `Scenario` (ABC) and must:
1212

1313
1. **Define `VERSION`** as a class constant (increment on breaking changes)
14-
2. **Implement three abstract methods:**
14+
2. **Optionally declare `BASELINE_POLICY`** (defaults to `BaselinePolicy.Enabled` — a baseline `PromptSendingAttack` is prepended and callers can opt out per run via `initialize_async(include_baseline=False)`):
15+
- `BaselinePolicy.Disabled` — baseline supported but off by default (e.g. `Jailbreak`, where templates dominate the run).
16+
- `BaselinePolicy.Forbidden` — baseline is meaningless for this scenario's comparison axis (e.g. `AdversarialBenchmark`, which compares against gold-standard answers). Explicit `include_baseline=True` raises `ValueError`.
17+
3. **Implement three abstract methods:**
1518

1619
```python
1720
class MyScenario(Scenario):
1821
VERSION: int = 1
22+
BASELINE_POLICY: ClassVar[BaselinePolicy] = BaselinePolicy.Enabled
1923

2024
@classmethod
2125
def get_strategy_class(cls) -> type[ScenarioStrategy]:
@@ -30,7 +34,7 @@ class MyScenario(Scenario):
3034
return DatasetConfiguration(dataset_names=["my_dataset"])
3135
```
3236

33-
3. **Optionally override `_get_atomic_attacks_async()`** — the base class provides a default
37+
4. **Optionally override `_get_atomic_attacks_async()`** — the base class provides a default
3438
that uses the factory/registry pattern (see "AtomicAttack Construction" below).
3539
Only override if your scenario needs custom attack construction logic.
3640

@@ -154,6 +158,8 @@ The default implementation:
154158
Only override when the scenario **cannot** use the factory/registry pattern — e.g., scenarios
155159
with custom composite logic, per-strategy converter stacks, or non-standard attack construction.
156160

161+
Overrides that want baseline support must emit it themselves by calling `self._build_baseline_atomic_attack(seed_groups=...)` with the same seeds used for the strategy attacks and prepending the result. The base implementation emits baseline automatically; passing freshly resolved seeds reintroduces ADO 9012 (baseline-vs-strategy population divergence under `max_dataset_size`).
162+
157163
### Manual AtomicAttack construction (for overrides):
158164

159165
```python

.github/workflows/build_and_test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ jobs:
3333
contents: read
3434
steps:
3535
- uses: actions/checkout@v5
36+
with:
37+
# Full history is required so pre-commit hooks (notably
38+
# enforce_alembic_revision_immutability) can compute merge-bases and
39+
# diff ranges against origin/main.
40+
fetch-depth: 0
3641

3742
- uses: actions/setup-python@v6
3843
with:

.pyrit_conf_example

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ operation: op_trash_panda
111111
# - /path/to/.env
112112
# - /path/to/.env.local
113113

114+
# Max Concurrent Scenario Runs
115+
# ----------------------------
116+
# Maximum number of scenario runs that can execute concurrently in the backend.
117+
# Applies only to the pyrit_backend server.
118+
max_concurrent_scenario_runs: 3
119+
120+
# Custom Initializer Registration (REST API)
121+
# -------------------------------------------
122+
# When true, the REST API accepts POST /api/initializers to register custom
123+
# initializer scripts and DELETE /api/initializers/{name} to remove custom
124+
# initializers.
125+
#
126+
# ⚠️ WARNING: Enabling this allows arbitrary Python code execution on the
127+
# server via the REST API. Only enable on trusted networks.
128+
# The pyrit_backend default host is localhost, which limits exposure.
129+
# If you bind to 0.0.0.0, ensure you are on a trusted network.
130+
#
131+
# Default: false
132+
allow_custom_initializers: false
133+
114134
# Silent Mode
115135
# -----------
116136
# If true, suppresses print statements during initialization.

build_scripts/enforce_alembic_revision_immutability.py

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,86 @@
44
"""
55
Migration history must be immutable. This hook enforces that by preventing deletion or updates to migration scripts.
66
7-
Checks both staged changes (local pre-commit) and the full branch diff against origin/main (CI).
7+
Checks staged changes (local pre-commit), the full branch diff against origin/main (CI PRs),
8+
and the previous commit (CI merge-queue / push-to-main).
89
"""
910

11+
import os
1012
import subprocess
1113
import sys
1214

1315
_VERSIONS_PATH = "pyrit/memory/alembic/versions/"
1416

1517

16-
def _git(*args: str) -> str:
17-
result = subprocess.run(["git", *args], capture_output=True, text=True)
18-
return result.stdout.strip()
18+
def _git(*args: str) -> subprocess.CompletedProcess[str]:
19+
return subprocess.run(["git", *args], capture_output=True, text=True)
1920

2021

21-
def _has_non_add_changes(diff_spec: list[str]) -> bool:
22-
output = _git("diff", "--name-status", *diff_spec, "--", _VERSIONS_PATH)
23-
return any(line and not line.startswith("A") for line in output.splitlines())
22+
def _git_stdout(*args: str) -> str:
23+
return _git(*args).stdout.strip()
24+
25+
26+
def _get_violations(diff_spec: list[str]) -> list[str]:
27+
"""Return lines from ``git diff --name-status`` that are not pure additions."""
28+
output = _git_stdout("diff", "--name-status", *diff_spec, "--", _VERSIONS_PATH)
29+
return [line for line in output.splitlines() if line and not line.startswith("A")]
30+
31+
32+
def _in_ci() -> bool:
33+
return os.environ.get("CI", "").lower() in {"1", "true"} or "GITHUB_ACTIONS" in os.environ
34+
35+
36+
def _fail_ci(reason: str) -> bool:
37+
"""Fail closed in CI when we can't perform the check, pass through locally."""
38+
if _in_ci():
39+
print(f"[ERROR] Cannot verify alembic revision immutability: {reason}")
40+
print(" Ensure the CI checkout has full history (fetch-depth: 0).")
41+
return True
42+
return False
2443

2544

2645
def has_revision_violations() -> bool:
2746
# Local pre-commit: check staged changes
28-
if _has_non_add_changes(["--cached"]):
47+
violations = _get_violations(["--cached"])
48+
if violations:
49+
_report(violations)
2950
return True
3051

31-
# CI: check full branch diff against origin/main
32-
merge_base = _git("merge-base", "origin/main", "HEAD")
33-
return bool(merge_base and _has_non_add_changes([f"{merge_base}...HEAD"]))
52+
# CI (PR): diff branch against its merge-base with origin/main.
53+
# The three-dot syntax (A...B) resolves to ``git diff $(merge-base A B) B``
54+
# automatically, so we don't need a separate merge-base call. When
55+
# origin/main is missing (shallow clone) git exits non-zero.
56+
pr_diff = _git("diff", "--name-status", "origin/main...HEAD", "--", _VERSIONS_PATH)
57+
if pr_diff.returncode == 0:
58+
violations = [line for line in pr_diff.stdout.strip().splitlines() if line and not line.startswith("A")]
59+
if violations:
60+
_report(violations)
61+
return True
62+
elif _fail_ci("origin/main is not available (shallow clone?)"):
63+
return True
64+
65+
# CI (merge-queue / push-to-main): on main the branch *is* origin/main, so
66+
# the diff above is empty. Compare HEAD against its first parent to catch
67+
# deletions or modifications introduced by the merge commit itself.
68+
head_parent = _git("rev-parse", "--verify", "HEAD~1")
69+
if head_parent.returncode == 0:
70+
violations = _get_violations(["HEAD~1..HEAD"])
71+
if violations:
72+
_report(violations)
73+
return True
74+
elif _fail_ci("HEAD~1 is not available (shallow clone?)"):
75+
return True
76+
77+
return False
78+
79+
80+
def _report(violations: list[str]) -> None:
81+
print("[ERROR] Migration scripts can only be added, not modified or deleted.")
82+
print("The following disallowed changes were detected:")
83+
for v in violations:
84+
print(f" {v}")
3485

3586

3687
if __name__ == "__main__":
3788
if has_revision_violations():
38-
print("[ERROR] Migration scripts can only be added, not modified or deleted.")
3989
sys.exit(1)

0 commit comments

Comments
 (0)