Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
501 commits
Select commit Hold shift + click to select a range
7a547aa
Say restore can't tell who wrote a saved file
varunj-msft Nov 10, 2026
f71259b
Merge the saved file wording
varunj-msft Nov 10, 2026
8902768
Merge the saved file wording
varunj-msft Nov 10, 2026
339fd15
Merge the restore builder changes
varunj-msft Nov 10, 2026
f1dcab1
Merge the saved file wording
varunj-msft Nov 10, 2026
fa8b6cb
Merge the restore builder changes
varunj-msft Nov 10, 2026
453a005
Merge the restore builder changes
varunj-msft Nov 10, 2026
11b17ad
Merge the restore builder changes
varunj-msft Nov 10, 2026
52da3fe
Merge the restore builder changes
varunj-msft Nov 10, 2026
9762bc9
Merge the restore builder changes
varunj-msft Nov 10, 2026
9b0187a
Merge the restore failure banner changes
varunj-msft Nov 10, 2026
986cc90
Test the parser's depth limit without depending on the stack size
varunj-msft Nov 10, 2026
1096925
Merge the parser depth test fix
varunj-msft Nov 10, 2026
f25b520
Merge the parser depth test fix
varunj-msft Nov 10, 2026
b7edc61
Merge the parser depth test fix
varunj-msft Nov 10, 2026
1845b64
Merge the parser depth test fix
varunj-msft Nov 10, 2026
bd2224b
Merge the parser depth test fix
varunj-msft Nov 10, 2026
b454e75
Merge the parser depth test fix
varunj-msft Nov 10, 2026
83a0bca
Merge the parser depth test fix
varunj-msft Nov 10, 2026
bd03690
Merge the parser depth test fix
varunj-msft Nov 10, 2026
2e9cbb0
Merge the parser depth test fix
varunj-msft Nov 10, 2026
71b8e33
Merge the parser depth test fix
varunj-msft Nov 10, 2026
b978528
Merge the parser depth test fix
varunj-msft Nov 10, 2026
42a8f91
Merge the parser depth test fix
varunj-msft Nov 10, 2026
218fb55
Merge the parser depth test fix
varunj-msft Nov 10, 2026
0c08d2f
Merge the parser depth test fix
varunj-msft Nov 10, 2026
c35bd81
Merge the parser depth test fix
varunj-msft Nov 10, 2026
fb90af8
Read saved entries in a worker thread when restore isn't given what w…
varunj-msft Nov 10, 2026
c44d2ce
Merge the worker-thread read
varunj-msft Nov 10, 2026
abd116a
Stay not ready when a saved entry can't be restored, and let apply retry
varunj-msft Nov 10, 2026
f94e981
Merge the not-ready restore
varunj-msft Nov 10, 2026
0bfa6cc
Drop the refusal of held names from launches
varunj-msft Nov 10, 2026
c62118f
Merge the not-ready restore
varunj-msft Nov 10, 2026
1fc0617
Show the restore failures while the runtime isn't ready
varunj-msft Nov 10, 2026
c0aa46d
Merge the not-ready restore
varunj-msft Nov 10, 2026
f59306e
Read saved recipes off the event loop by default, and harden URL reda…
richlundeen Nov 10, 2026
7d90d68
Merge the not-ready restore
varunj-msft Nov 10, 2026
0f0c53d
Treat a class a launch script puts in place of another as temporary
varunj-msft Nov 10, 2026
26e1d0e
Merge the not-ready restore
varunj-msft Nov 10, 2026
2ea542b
Merge the not-ready restore
varunj-msft Nov 10, 2026
9fca651
Merge the not-ready restore
varunj-msft Nov 10, 2026
0ba47e8
Merge the not-ready restore
varunj-msft Nov 10, 2026
130f6e2
Merge the not-ready restore
varunj-msft Nov 10, 2026
3017a37
Merge the not-ready restore
varunj-msft Nov 10, 2026
f1ff143
Keep an "@" in a URL's path when redacting URLs in text
richlundeen Nov 10, 2026
ae71a47
Check a saved technique's attack parameters as restore does
varunj-msft Nov 10, 2026
1753eef
Merge the not-ready restore
varunj-msft Nov 10, 2026
d4d703b
Merge the not-ready restore
varunj-msft Nov 10, 2026
dc88b06
Merge the not-ready restore
varunj-msft Nov 10, 2026
bef063c
Let saved entries be deleted while the runtime isn't ready
varunj-msft Nov 10, 2026
009e761
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
94f8aa5
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
21f22a4
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
e9103ff
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
57b069a
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
7c88228
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
616a7df
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
fede632
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
aaae79f
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
9214fa1
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
ed59b8b
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
a23eec1
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
4d82205
Show the restore failures to administrators in Configuration
varunj-msft Nov 10, 2026
c797b75
Merge the restore read and redaction fixes
varunj-msft Nov 10, 2026
a724a05
Merge the restore failures in Configuration
varunj-msft Nov 10, 2026
b757dc3
Delete saved entries that couldn't be restored from Configuration
varunj-msft Nov 10, 2026
cc4ae4d
Merge main
varunj-msft Nov 10, 2026
270a1f0
Merge main
varunj-msft Nov 10, 2026
9f4c71e
Merge main
varunj-msft Nov 10, 2026
5121026
Merge main
varunj-msft Nov 10, 2026
d15f7fd
Merge main
varunj-msft Nov 11, 2026
84ad3fe
Merge main
varunj-msft Nov 11, 2026
ddfe03c
Merge main
varunj-msft Nov 11, 2026
7104a25
Merge main
varunj-msft Nov 11, 2026
b01bd1a
Merge main
varunj-msft Nov 11, 2026
2554f78
Merge main
varunj-msft Nov 11, 2026
3c34248
Merge main
varunj-msft Nov 11, 2026
a363e59
Merge main
varunj-msft Nov 11, 2026
be0b885
Use the dialog's save button in the new tests from main
varunj-msft Nov 11, 2026
7b08ca0
Press the hidden-targets button in the saved-by test
varunj-msft Nov 11, 2026
6c8d33a
Merge the dialog test fixes
varunj-msft Nov 11, 2026
cabb56f
Merge the dialog test fixes
varunj-msft Nov 11, 2026
b709141
Merge the dialog test fixes
varunj-msft Nov 11, 2026
2cff4c4
Merge the dialog test fixes
varunj-msft Nov 11, 2026
1c26722
Merge the dialog test fixes
varunj-msft Nov 11, 2026
1d8ec10
Merge the dialog test fixes
varunj-msft Nov 11, 2026
8a64cbf
Merge main
varunj-msft Nov 11, 2026
cae9797
Merge main
varunj-msft Nov 11, 2026
187191c
Answer suite readiness storage failures through the shared storage ha…
varunj-msft Nov 11, 2026
ee8162c
Give the HTTPTarget save test the connection field a create now needs
varunj-msft Nov 11, 2026
3fb2e7a
Merge the readiness fix
varunj-msft Nov 11, 2026
bb2a620
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
db7451a
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
b200c92
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
4c42a6b
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
43096e8
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
22b057c
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
9909158
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
77b80f4
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
9a48180
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
cffb818
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
71713a0
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
beebe73
Merge the readiness and save test fixes
varunj-msft Nov 11, 2026
374c620
Merge main
varunj-msft Nov 11, 2026
7ac059c
Answer suite readiness storage failures through the shared storage ha…
varunj-msft Nov 11, 2026
dc0dda8
Press the dialog's save button in the nullable parameters test from main
varunj-msft Nov 11, 2026
8143c1a
Merge the converter dialog test fix
varunj-msft Nov 11, 2026
75ee2ea
Merge the converter dialog test fix
varunj-msft Nov 11, 2026
ba4694e
Merge the converter dialog test fix
varunj-msft Nov 11, 2026
e08b67a
Merge main
varunj-msft Nov 11, 2026
7fa4ca4
Expect storage errors from main's version-checked delete tests the wa…
varunj-msft Nov 11, 2026
9bf5886
Merge main
varunj-msft Nov 11, 2026
0e87a61
Merge main
varunj-msft Nov 11, 2026
626d2d4
Merge main
varunj-msft Nov 11, 2026
4a86017
Merge main
varunj-msft Nov 11, 2026
9568edb
Merge main
varunj-msft Nov 11, 2026
54e4374
Merge main
varunj-msft Nov 11, 2026
0d97131
Merge main
varunj-msft Nov 11, 2026
2849ae8
Merge main
varunj-msft Nov 11, 2026
c9f0f7b
Merge main
varunj-msft Nov 11, 2026
ace84a9
Merge main
varunj-msft Nov 11, 2026
a8772db
Merge main
varunj-msft Nov 11, 2026
82893e8
Merge main
varunj-msft Nov 11, 2026
9e47a20
Merge main
varunj-msft Nov 11, 2026
cd98334
Merge main
varunj-msft Nov 11, 2026
e34258c
Fit the explicit-target E2E from main to the dialog's save button and…
varunj-msft Nov 11, 2026
bf91430
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
a9dccb3
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
9e96747
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
e738666
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
356bdef
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
fa7c74f
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
50817c0
Merge the explicit-target E2E fix
varunj-msft Nov 11, 2026
c506195
Merge main
varunj-msft Nov 11, 2026
1ba526f
Merge main
varunj-msft Nov 11, 2026
9cdf7e7
Merge main
varunj-msft Nov 11, 2026
40d36ed
Merge main
varunj-msft Nov 11, 2026
f1a2756
Merge main
varunj-msft Nov 11, 2026
6a52054
Merge main
varunj-msft Nov 11, 2026
23a7ec1
Merge main
varunj-msft Nov 11, 2026
70fbc3d
Merge main
varunj-msft Nov 11, 2026
a1e79d1
Merge main
varunj-msft Nov 11, 2026
a5d42d1
Merge main
varunj-msft Nov 11, 2026
2402df2
Merge main
varunj-msft Nov 11, 2026
1b2ea03
Merge main
varunj-msft Nov 11, 2026
10c488f
Merge main
varunj-msft Nov 11, 2026
558a54c
Merge main
varunj-msft Nov 11, 2026
942adfc
Merge main
varunj-msft Nov 11, 2026
aadbb57
Merge main
varunj-msft Nov 11, 2026
20defb2
Merge main
varunj-msft Nov 11, 2026
8c517d0
Use the dialog's save button in main's endpoint tests
varunj-msft Nov 11, 2026
1137977
Merge the endpoint test fix
varunj-msft Nov 11, 2026
aaefa8f
Merge the endpoint test fix
varunj-msft Nov 11, 2026
6356a56
Merge the endpoint test fix
varunj-msft Nov 11, 2026
9f3e096
Merge the endpoint test fix
varunj-msft Nov 11, 2026
b4451cc
Merge the endpoint test fix
varunj-msft Nov 11, 2026
3f6a2c3
Merge the endpoint test fix
varunj-msft Nov 11, 2026
7046bc9
Merge the endpoint test fix
varunj-msft Nov 11, 2026
a458ba6
Say that the saved-entry routes also answer while restore failed
varunj-msft Nov 11, 2026
0e7a21f
Exempt the storage error handler from the keyword-only check
varunj-msft Nov 11, 2026
891e8d6
Make get_target's parameters keyword-only
varunj-msft Nov 11, 2026
14f105f
Make the converter, scorer and technique GET parameters keyword-only
varunj-msft Nov 11, 2026
92fd4c3
Make the saved-entry route parameters keyword-only
varunj-msft Nov 11, 2026
c35fa01
Merge keyword-only fixes
varunj-msft Nov 11, 2026
953dcce
Merge keyword-only fixes
varunj-msft Nov 11, 2026
d6079a1
Merge keyword-only fixes
varunj-msft Nov 11, 2026
b01b213
Merge keyword-only fixes
varunj-msft Nov 11, 2026
21556ed
Merge keyword-only fixes
varunj-msft Nov 11, 2026
14004e9
Merge keyword-only fixes
varunj-msft Nov 11, 2026
3abc824
Merge keyword-only fixes
varunj-msft Nov 11, 2026
623fd40
Merge keyword-only fixes
varunj-msft Nov 11, 2026
2d74284
Merge keyword-only fixes
varunj-msft Nov 11, 2026
1fc386a
Merge keyword-only fixes
varunj-msft Nov 11, 2026
ee0a223
Merge keyword-only fixes
varunj-msft Nov 11, 2026
779f0c7
Merge keyword-only fixes
varunj-msft Nov 11, 2026
3c430a9
Merge keyword-only fixes
varunj-msft Nov 11, 2026
f0b87c1
Merge keyword-only fixes
varunj-msft Nov 11, 2026
d466ca9
Merge keyword-only fixes
varunj-msft Nov 11, 2026
03ff2c9
Merge keyword-only fixes
varunj-msft Nov 11, 2026
bf97225
Log every problem with a saved file that isn't valid
varunj-msft Nov 11, 2026
8ed0f33
Merge the full schema-validation log
varunj-msft Nov 11, 2026
5e6faed
Merge the full schema-validation log
varunj-msft Nov 11, 2026
7625f89
Merge the full schema-validation log
varunj-msft Nov 11, 2026
8dbd4c2
Merge the full schema-validation log
varunj-msft Nov 11, 2026
5c7223a
Merge the full schema-validation log
varunj-msft Nov 11, 2026
8676ee5
Merge the full schema-validation log
varunj-msft Nov 11, 2026
74e1310
Merge the full schema-validation log
varunj-msft Nov 11, 2026
072a560
Merge the full schema-validation log
varunj-msft Nov 11, 2026
32e088c
Merge the full schema-validation log
varunj-msft Nov 11, 2026
509f6a3
Merge the full schema-validation log
varunj-msft Nov 11, 2026
fb98481
Merge the full schema-validation log
varunj-msft Nov 11, 2026
fd9c379
Merge the full schema-validation log
varunj-msft Nov 11, 2026
183c640
Merge the full schema-validation log
varunj-msft Nov 11, 2026
6cb8285
Drop the restore report once live apply starts replacing the runtime
varunj-msft Nov 12, 2026
35199cc
Leave the error behind a storage failure out of the logs
varunj-msft Nov 12, 2026
f7857ab
Import torch off the event loop before building a HuggingFaceChatTarget
varunj-msft Nov 12, 2026
efcdfa9
Test a blob upload that fails with a transport or authentication error
varunj-msft Nov 12, 2026
0fdebdc
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
c413994
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
c057e30
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
3614208
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
dc97ec7
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
a9478df
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
dc0c058
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
8f27c91
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
4f2208d
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
d22d466
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
167dd8e
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
dfc704c
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
bb6bc88
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
9611034
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
afb86cc
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
d274900
Merge the storage log and restore report fixes
varunj-msft Nov 12, 2026
7f416de
Log the error behind a storage failure with URL credentials removed
varunj-msft Nov 12, 2026
12239e6
Read use_cuda through its registered parameter before checking for CUDA
varunj-msft Nov 12, 2026
1d98920
Say that live apply needs one backend worker and one replica
varunj-msft Nov 12, 2026
c9b795a
Merge main
varunj-msft Nov 12, 2026
38c981c
Merge main
varunj-msft Nov 12, 2026
eb71448
Merge main
varunj-msft Nov 12, 2026
1be067e
Merge main
varunj-msft Nov 12, 2026
6ba4a7b
Merge main
varunj-msft Nov 12, 2026
162fc2a
Merge main
varunj-msft Nov 12, 2026
6c0c3dd
Merge main
varunj-msft Nov 12, 2026
3d0950d
Merge main
varunj-msft Nov 12, 2026
8b59c5d
Merge main
varunj-msft Nov 12, 2026
d1504ec
Merge main
varunj-msft Nov 12, 2026
457563c
Merge main
varunj-msft Nov 12, 2026
8353569
Merge main
varunj-msft Nov 12, 2026
1214160
Merge main
varunj-msft Nov 12, 2026
b9a4ae4
Merge main
varunj-msft Nov 12, 2026
6db01ee
Merge main
varunj-msft Nov 12, 2026
798bc9d
Merge main
varunj-msft Nov 12, 2026
38ab767
Merge main
varunj-msft Nov 12, 2026
9ffacc7
Point the restore banner at the saved files, and let it wrap
varunj-msft Nov 12, 2026
afd9576
Keep the restore failure list and runtime status inside their column …
varunj-msft Nov 12, 2026
498ea1e
Merge the restore banner and list layout fixes
varunj-msft Nov 12, 2026
81992d4
Keep the whole runtime bar multiline, since its text changes after it…
varunj-msft Nov 12, 2026
3987bc2
Merge the multiline runtime bar
varunj-msft Nov 12, 2026
5c9bee8
Say failed entries can be deleted in Configuration, and let the not-r…
varunj-msft Nov 12, 2026
4e8d9c2
Return 409 when a suite's launch scripts would replace a saved entry
varunj-msft Nov 12, 2026
b1bbbe6
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
9ce29cb
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
4e0e640
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
0b9efb3
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
28c9398
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
359553a
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
dcc24fd
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
478b640
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
4ea1e15
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
fb5fed6
Merge the suite launch conflict status
varunj-msft Nov 12, 2026
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
15 changes: 15 additions & 0 deletions .pyrit_conf_example
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,21 @@ allow_custom_initializers: false
# as scenario_presets_source. Defaults to ~/.pyrit/scenario_suites.
# scenario_suites_source: https://account.blob.core.windows.net/container/scenario_suites

# Optional storage for saved targets, converters, scorers, and techniques, so they
# come back when the backend restarts. Each kind has its own folder under the source.
# This supports the same local directory and Azure Blob sources as
# scenario_presets_source. If any saved entry can't be restored, the backend stays
# not ready and lists the failures in its runtime status. Fix or delete the listed
# files, then apply the configuration or restart the backend. Defaults to
# ~/.pyrit/registry_recipes.
#
# ⚠️ WARNING: The backend rebuilds every saved entry in this source as an
# administrator, at startup and live reinitialization. Anyone who can write to this
# source can make the backend run a model's repository code or use its identity.
# Restrict write access to trusted administrators, and use a dedicated folder or
# blob prefix.
# registry_recipes_source: https://account.blob.core.windows.net/container/registry_recipes

# Upload Folder for API-Created Targets
# -------------------------------------
# Folder that targets created through the REST API may upload local files from
Expand Down
14 changes: 14 additions & 0 deletions doc/getting_started/pyrit_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,20 @@ Suites store ordered references to scenario presets and resolve each preset's cu

A SAS query string may be included; otherwise, PyRIT uses `DefaultAzureCredential`. The default is `~/.pyrit/scenario_suites`. Local writes use the same hidden lock-file and atomic replacement behavior as scenario presets. Startup scans the configured source so malformed suite documents are reported immediately without preventing the backend from starting.

### `registry_recipes_source`

Stores saved targets, converters, scorers, and techniques in a local directory or Azure Blob container, so they come back when the backend restarts. Targets, converters, and scorers created through `POST /api/targets`, `POST /api/converters`, and `POST /api/scorers` with `"persist": true` are saved here. An Azure URI may include a blob-name prefix:

```yaml
registry_recipes_source: https://account.blob.core.windows.net/pyrit-storage/registry-recipes
```

Each kind has its own folder under the source (`targets/`, `converters/`, `scorers/`, `techniques/`), with one JSON file per saved entry named after the entry. A file holds the create request the entry was made with, and restore builds the entry through the API's create for its kind. A file never holds a credential: restore refuses a file that stores a value for a parameter its class marks as a credential, such as `api_key`. A saved target also can't use a key from this server: it signs in with Microsoft Entra ID (`"auth_mode": "identity"`), which uses this server's Azure identity for everyone using the app, or needs no key. Targets from the server's configuration that it uses, such as a round-robin's members, keep their own sign-in. So restore refuses a target file that would read an empty key from an environment variable or leave an address to this server's settings. The addresses each target type declares (such as `endpoint`, `container_url` for Blob storage, or an A2A agent card path) can't hold a user name, password, SAS token, fragment, path parameters (`;`, `&`, `=`), or a query other than `api-version`, and a path can't have a query at all. An address the server would otherwise fill in must be a full http(s) or ws(s) address. In identity mode, an OpenAI target file sets `"headers": "{}"` in its `params` so that the server's `OPENAI_ADDITIONAL_REQUEST_HEADERS` aren't added. The OpenAI SDK adds the headers in `OPENAI_CUSTOM_HEADERS` to every request, so while the server sets it, OpenAI targets can't be saved and saved ones aren't restored. The SDK's `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` still apply to every OpenAI target on this server. GitHub Copilot, Hugging Face, HackAPrompt, LiteLLM, HTTP, and Copilot WebSocket targets can't be saved yet. Converters and scorers follow the same rules. The Azure Speech converters, the Azure Content Safety scorer, and the audio scorers can't be saved yet, because they sign in with this server's key or Azure identity, and neither can the local refusal classifier scorer, whose model can come from this server. A SAS query string may be included in the source; otherwise, PyRIT uses `DefaultAzureCredential`. The default is `~/.pyrit/registry_recipes`.

Restore can't tell who wrote a saved file, even one that names the user who saved it, so the backend rebuilds every entry in this source as an administrator, at startup and after a live apply. A file here can, for example, make the backend run a model's repository code, or use the server's identity on a storage container that the file names. Write access to this source is administrator authority on the backend: grant it only to principals you already trust as backend administrators, and use a dedicated directory or blob prefix that less-trusted users and services cannot write to.

The backend restores saved entries at startup and after a live apply, once the configured initializers have registered theirs. If any saved entry can't be restored, the backend doesn't become ready (state `failed`, outcome `restore-failed`), so a scan can't quietly run without an entry it expects. Until then, only the configuration, status and saved-entry routes answer; the rest return 503. Administrators see each failure with its reason in `GET /api/config/runtime` and on the GUI's Configuration page, where they can fix the listed files or delete them with **Delete** (`DELETE /api/saved-entries/{kind}/{name}`); everyone else sees that an administrator has to fix saved entries. When a folder couldn't be listed, check that the backend can read this source. Then apply the configuration, if `enable_live_reinitialization` is on and the backend runs as one worker and one replica, which live apply requires; otherwise, restart the backend. Files whose names aren't valid entry names (up to 64 lowercase letters, digits, and underscores, starting with a letter) aren't failures and don't block startup; administrators can see them under `restore_ignored` in `GET /api/config/runtime`.

### `initialization_scripts`

Local paths to custom Python scripts containing `PyRITInitializer` subclasses. Paths can be absolute or relative to the current working directory.
Expand Down
53 changes: 51 additions & 2 deletions doc/gui/0_gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ members; defaults, visibility, and the target's configuration columns remain
available. Converter inputs and outputs stay visible in the table. Use the small
red X to remove a converter from the live registry; confirmation is required.

**Delete** on a saved target, converter, scorer, or technique deletes its saved
file and removes it from the registry, so it doesn't come back after a restart.
The person who saved it, or an administrator, can delete it; anyone can when
sign-in is off. The dialog says who saved it. An entry that another saved entry
uses, or that a registered entry holds, isn't deleted until that one is deleted
or changed, and an entry isn't deleted while a run, send, or request could be
using it. If the file changed since the dialog read it, **Look again** shows the
current one. A saved entry that couldn't be restored keeps PyRIT from becoming
ready; an administrator can delete it from the list on the **Configuration**
page, then select **Reinitialize PyRIT** or restart the backend.

**Scenario presets** save a scenario configuration for reuse with a target
chosen at launch. **Suites** maintain ordered collections of preset definitions.
They are stored separately from live component instances.
Expand All @@ -209,8 +220,46 @@ Editing a preset preserves unchanged parameter values exactly, including commas
and whitespace inside list entries, empty lists, duplicates, and their order.
Editing a list field uses the comma-separated format shown in the editor.

Targets, converters, and techniques added only to the live registry must be
recreated after a backend restart or reinitialization. Use
**New Target** saves the target, so it comes back after the backend restarts or
is reinitialized. Give it a registry name of lowercase letters, digits, and
underscores that starts with a letter; the dialog suggests one from the target
type and model. Saved targets are kept in `registry_recipes_source`
(`~/.pyrit/registry_recipes` unless set in your `.pyrit_conf`). To remove a saved
target, select **Delete** on its row in the Targets table. If a startup initializer later
registers a target under a saved target's name, the saved one isn't restored and
the backend isn't ready until that file is renamed or removed and the backend
restarted.

Saved targets can't hold credentials yet, so choose Identity-based or No key
authentication when the target supports it. A target that needs an API key or
another credential value, a type that can't be saved, or a round robin that uses
a Temporary target is created as **Temporary** instead, and the dialog says why.
A Temporary target is gone after the backend restarts or is reinitialized.

The Targets table labels each target **Saved**, **From configuration**
(registered by an initializer at startup), or **Temporary**. A target saved while
signed in also says who saved it (**Saved by you** for your own). Once you have
saved one, check **Only targets I saved** to list just yours. Anyone can still
use every target.

New converters are saved the same way, wherever you add one: **New Converter** in
the Converters tab, the converter panel in chat, or **New converter** in the
technique editor. The dialog suggests a name such as `base64_converter`. A
converter type that can't be saved yet (the Azure Speech converters, and types
that need an uploaded file), a converter with an uploaded file, or one that uses a
Temporary target is created as **Temporary** instead, and the dialog says why.
**Save to registry** on a converter's changed settings, in chat or in a technique
edit, makes a Temporary copy. The Converters and Scorers tables use the same three
labels and say who saved each entry, with **Only converters I saved** and **Only
scorers I saved** to list just yours; scorers can be saved through the API.

**New technique** saves the technique the same way. Give it a registry name of
lowercase letters, digits, and underscores that starts with a letter. A technique
that uses a Temporary converter or a Temporary adversarial target is created as
**Temporary** instead, and the dialog says why. Settings changed for a single run
in Scanner stay with that run. The Techniques table uses the same three labels and
**Only techniques I saved**. To
remove a saved technique, select **Delete** next to its name. Use
[initializers](../code/setup/0_setup.md) for repeatable component setup.

Use **Auto-detect capabilities** to select targets and run live checks. Results
Expand Down
9 changes: 9 additions & 0 deletions frontend/e2e/_targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface FlatTarget {
capabilities?: unknown;
target_specific_params?: unknown;
inner_targets?: FlatTarget[] | null;
origin?: "saved" | "configuration" | "temporary" | null;
saved_by?: string | null;
saved_by_me?: boolean;
}

interface TargetInstanceMock {
Expand All @@ -27,6 +30,9 @@ interface TargetInstanceMock {
capabilities: unknown;
target_specific_params: unknown;
inner_targets: TargetInstanceMock[] | null;
origin?: "saved" | "configuration" | "temporary" | null;
saved_by?: string | null;
saved_by_me?: boolean;
}

export function makeTarget(flat: FlatTarget): TargetInstanceMock {
Expand All @@ -41,5 +47,8 @@ export function makeTarget(flat: FlatTarget): TargetInstanceMock {
capabilities: flat.capabilities ?? null,
target_specific_params: flat.target_specific_params ?? null,
inner_targets: flat.inner_targets ? flat.inner_targets.map(makeTarget) : null,
...(flat.origin !== undefined ? { origin: flat.origin } : {}),
...(flat.saved_by !== undefined ? { saved_by: flat.saved_by } : {}),
...(flat.saved_by_me !== undefined ? { saved_by_me: flat.saved_by_me } : {}),
};
}
66 changes: 61 additions & 5 deletions frontend/e2e/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ test.describe("Target Registry Page", () => {

// Dialog should open
await expect(page.getByText("Create New Target")).toBeVisible();
await expect(page.getByText("Create Target")).toBeVisible();
await expect(page.getByRole("button", { name: "Save Target" })).toBeVisible();
});

test("should update targets after a visible minute without a Refresh control", async ({ page }) => {
Expand Down Expand Up @@ -343,6 +343,34 @@ test.describe("Target Registry Page", () => {
await expect(reset).toBeFocused();
await expect(reset).toHaveAttribute("aria-disabled", "true");
});

test("should say who saved each target and list only yours on request", async ({ page }) => {
await page.route(/\/api\/targets(?:\?.*)?$/, async (route) => {
await route.fulfill(
mockTargetsList([
{ target_registry_name: "mine_chat", target_type: "OpenAIChatTarget", model_name: "gpt-4o", origin: "saved",
saved_by: "Ada", saved_by_me: true },
{ target_registry_name: "bobs_chat", target_type: "OpenAIChatTarget", model_name: "gpt-4o-mini",
origin: "saved", saved_by: "Bob" },
{ target_registry_name: "startup_chat", target_type: "OpenAIChatTarget", model_name: "gpt-4.1",
origin: "configuration" },
])
);
});
await goToTargets(page);

await expect(page.getByTestId("target-row-mine_chat").getByText("Saved by you", { exact: true })).toBeVisible();
await expect(page.getByTestId("target-row-bobs_chat").getByText("Saved by Bob", { exact: true })).toBeVisible();
await page.getByRole("checkbox", { name: "Only targets I saved (1)" }).check();
await expect(page.getByTestId("target-row-bobs_chat")).toHaveCount(0);
await expect(page.getByTestId("target-row-startup_chat")).toHaveCount(0);
await expect(page.getByTestId("target-row-mine_chat")).toBeVisible();

// The choice isn't kept, so a fresh load lists everyone's targets again.
await goToTargets(page);
await expect(page.getByTestId("target-row-bobs_chat")).toBeVisible();
await expect(page.getByRole("checkbox", { name: "Only targets I saved (1)" })).not.toBeChecked();
});
});

test.describe("Create Target Dialog", () => {
Expand Down Expand Up @@ -406,12 +434,14 @@ test.describe("Create Target Dialog", () => {
await expect(selectedDetails).toContainText("Supported authentication: API key");
});

test("should create a target through the dialog", async ({ page }) => {
test("should save a target through the dialog", async ({ page }) => {
let createdTarget: FlatTarget | null = null;
let createBody: Record<string, unknown> | null = null;

await page.route(/\/api\/targets/, async (route) => {
if (route.request().method() === "POST") {
const body = JSON.parse(route.request().postData() ?? "{}");
createBody = body;
createdTarget = {
target_registry_name: "new-target-1",
target_type: body.type,
Expand All @@ -429,6 +459,27 @@ test.describe("Create Target Dialog", () => {
await route.fulfill(mockTargetsList(items));
}
});
// Registered last, so it answers the type list instead of the route above.
await page.route(/\/api\/targets\/types(?:\?.*)?$/, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [
{
target_type: "OpenAIChatTarget",
parameters: [
{ name: "endpoint", type_name: "str", required: true, default: null },
{ name: "model_name", type_name: "str", required: true, default: null },
],
supported_auth_modes: ["api_key", "identity", "none"],
saveable_auth_modes: ["identity", "none"],
not_saveable_reason: null,
},
],
}),
});
});

await goToTargets(page);

Expand All @@ -453,11 +504,16 @@ test.describe("Create Target Dialog", () => {
// Fill model name
await dialog.getByPlaceholder("e.g. gpt-4o, my-deployment").fill("gpt-4o-test");

// Click Create Target
await dialog.getByRole("button", { name: "Create Target" }).click();
await dialog.getByRole("button", { name: "Save Target" }).click();

// Dialog should close and target should appear in the list
await expect(page.getByText("Create New Target")).not.toBeVisible({ timeout: 5_000 });
expect(createBody).toMatchObject({
name: "openai_chat_gpt_4o_test",
type: "OpenAIChatTarget",
auth_mode: "identity",
persist: true,
});
await expect(page.getByText("gpt-4o-test", { exact: true })).toBeVisible({ timeout: 10_000 });
await expect(page.getByText("OpenAIChatTarget")).toBeVisible();
});
Expand Down Expand Up @@ -496,7 +552,7 @@ test.describe("Create Target Dialog", () => {
await expect(page.getByText("Create New Target")).toBeVisible();

const dialog = page.locator('[role="dialog"]');
const createBtn = dialog.getByRole("button", { name: "Create Target" });
const createBtn = dialog.getByRole("button", { name: "Save Target" });
await expect(createBtn).toBeDisabled();

await selectTargetType(
Expand Down
30 changes: 17 additions & 13 deletions frontend/e2e/fixtures/manual_send_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""Offline backend for manual-send browser tests, with no real configuration or providers."""

import os
import tempfile
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, patch
Expand All @@ -27,19 +28,22 @@ async def _initialize_offline_async(self: ConfigurationLoader, *, raise_on_initi

@asynccontextmanager
async def _offline_lifespan_async(application: FastAPI) -> AsyncGenerator[None, None]:
config = ConfigurationLoader(memory_db_type="in_memory", env_files=[], env_akv_ref=[])
with (
patch.dict(os.environ, {"PYRIT_AUTH_MODE": "local", "PYRIT_DEV_MODE": "true"}, clear=True),
patch.object(
ConfigurationFileService,
"_read_source_async",
new=AsyncMock(return_value="memory_db_type: in_memory\nenv_files: []\ninitializers: []\n"),
),
patch.object(ConfigurationLoader, "load_with_overrides", return_value=config),
patch.object(ConfigurationLoader, "initialize_pyrit_async", new=_initialize_offline_async),
):
async with lifespan(application):
yield
with tempfile.TemporaryDirectory() as recipes:
config = ConfigurationLoader(
memory_db_type="in_memory", env_files=[], env_akv_ref=[], registry_recipes_source=recipes
)
with (
patch.dict(os.environ, {"PYRIT_AUTH_MODE": "local", "PYRIT_DEV_MODE": "true"}, clear=True),
patch.object(
ConfigurationFileService,
"_read_source_async",
new=AsyncMock(return_value="memory_db_type: in_memory\nenv_files: []\ninitializers: []\n"),
),
patch.object(ConfigurationLoader, "load_with_overrides", return_value=config),
patch.object(ConfigurationLoader, "initialize_pyrit_async", new=_initialize_offline_async),
):
async with lifespan(application):
yield


app.router.lifespan_context = _offline_lifespan_async
Loading
Loading