Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
624 commits
Select commit Hold shift + click to select a range
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
dd7c52f
Tell an unreadable scenario preset apart from a preset storage outage
varunj-msft Nov 12, 2026
8c0ba31
Merge the unreadable preset message
varunj-msft Nov 12, 2026
bd859ee
Merge main
varunj-msft Nov 12, 2026
15e00de
Merge main
varunj-msft Nov 12, 2026
0c5baa8
Merge main
varunj-msft Nov 12, 2026
a3f65ad
Merge main
varunj-msft Nov 12, 2026
dd7358b
Merge main
varunj-msft Nov 12, 2026
fc37262
Merge main
varunj-msft Nov 12, 2026
8618ce3
Merge main
varunj-msft Nov 12, 2026
836d0a3
Merge main
varunj-msft Nov 12, 2026
fe96339
Merge main
varunj-msft Nov 12, 2026
c1b411d
Merge main
varunj-msft Nov 12, 2026
f55ec93
Merge main
varunj-msft Nov 12, 2026
48cd409
Test the torch warm-up without torch and with a use_cuda that isn't a…
varunj-msft Nov 12, 2026
6f40045
Merge the torch warm-up tests
varunj-msft Nov 12, 2026
d4337d8
Merge main
varunj-msft Nov 12, 2026
48bc6d1
Merge main
varunj-msft Nov 12, 2026
9789264
Merge main
varunj-msft Nov 12, 2026
42b9853
Merge main
varunj-msft Nov 12, 2026
fd7a648
Merge main
varunj-msft Nov 12, 2026
9d5293a
Merge main
varunj-msft Nov 12, 2026
81008a4
Merge main
varunj-msft Nov 12, 2026
00dbfa8
Merge main
varunj-msft Nov 12, 2026
e134538
Merge main
varunj-msft Nov 12, 2026
77ef9e7
Prepare a Hugging Face target in the target library instead of the ba…
varunj-msft Nov 12, 2026
879f551
Merge main
varunj-msft Nov 12, 2026
867c9a0
Merge the Hugging Face preparation move
varunj-msft Nov 12, 2026
ffc853c
Merge main
varunj-msft Nov 12, 2026
805ca2a
Merge main into part 7
varunj-msft Nov 13, 2026
b9c50e1
Merge part 7 into part 8
varunj-msft Nov 13, 2026
5363c0b
Merge part 8 into part 9
varunj-msft Nov 13, 2026
d50f393
Merge part 9 into part 10
varunj-msft Nov 13, 2026
1c80cf5
Merge part 10 into part 11
varunj-msft Nov 13, 2026
84c564c
Merge part 11 into part 12
varunj-msft Nov 13, 2026
e33ca58
Merge part 12 into part 13
varunj-msft Nov 13, 2026
fe0efcb
Merge part 13 into part 14
varunj-msft Nov 13, 2026
898cb7a
Merge part 14 into part 15
varunj-msft Nov 13, 2026
4ac01f9
Merge part 15 into part 20
varunj-msft Nov 13, 2026
cdbe6ad
Keep the recipe storage setup apart from the saved target requirements
varunj-msft Nov 13, 2026
450b1e5
Merge part 7 into part 8
varunj-msft Nov 13, 2026
699ab5b
Move saving a target into the registry library
varunj-msft Nov 13, 2026
7d914b1
Merge part 8 into part 9
varunj-msft Nov 13, 2026
b008ce5
Merge part 9 into part 10
varunj-msft Nov 13, 2026
47ba126
Merge part 10 into part 11
varunj-msft Nov 13, 2026
59794d4
Merge part 11 into part 12
varunj-msft Nov 13, 2026
713f7d1
Merge main into part 8
varunj-msft Nov 13, 2026
793316a
Merge part 8 into part 9
varunj-msft Nov 13, 2026
4ca4f25
Merge part 9 into part 10
varunj-msft Nov 13, 2026
e4f32e3
Merge part 10 into part 11
varunj-msft Nov 13, 2026
1dbebcb
Merge part 11 into part 12
varunj-msft Nov 13, 2026
922696b
Merge part 12 into part 13
varunj-msft Nov 13, 2026
e87d125
Merge part 13 into part 14
varunj-msft Nov 13, 2026
888d939
Merge part 14 into part 15
varunj-msft Nov 13, 2026
3c5fb36
Merge part 15 into part 20
varunj-msft Nov 13, 2026
2e2e85d
Merge main into part 11
varunj-msft Nov 13, 2026
a2ce936
Merge part 11 into part 12
varunj-msft Nov 13, 2026
33f082b
Merge part 12 into part 13
varunj-msft Nov 13, 2026
3e0832d
Merge part 13 into part 14
varunj-msft Nov 13, 2026
6ec7ad5
Merge part 14 into part 15
varunj-msft Nov 13, 2026
2821e88
Merge part 15 into part 20
varunj-msft Nov 13, 2026
048283e
Merge main into part 8
varunj-msft Nov 13, 2026
6c243b6
Merge part 8 into part 9
varunj-msft Nov 13, 2026
1411c5d
Merge part 9 into part 10
varunj-msft Nov 13, 2026
b248a32
Merge part 10 into part 11
varunj-msft Nov 13, 2026
c283787
Merge part 11 into part 12
varunj-msft Nov 13, 2026
3905a7e
Merge part 12 into part 13
varunj-msft Nov 13, 2026
fbc0988
Merge part 13 into part 14
varunj-msft Nov 13, 2026
d5704e5
Merge part 14 into part 15
varunj-msft Nov 13, 2026
c0528c2
Merge part 15 into part 20
varunj-msft Nov 13, 2026
f273bff
Merge main into part 8
varunj-msft Nov 14, 2026
d692b25
Merge main into part 9
varunj-msft Nov 14, 2026
dc2a52c
Merge main into part 10
varunj-msft Nov 14, 2026
24bf3b2
Merge main into part 11
varunj-msft Nov 14, 2026
c983027
Merge main into part 12
varunj-msft Nov 14, 2026
ddca4a1
Merge main into part 13
varunj-msft Nov 14, 2026
eb21767
Merge main into part 14
varunj-msft Nov 14, 2026
ba2d0ee
Merge main into part 15
varunj-msft Nov 14, 2026
36a5a8d
Merge main into part 20
varunj-msft Nov 14, 2026
54d9332
Check saved entries against restore's inputs and settle cancelled saves
varunj-msft Nov 14, 2026
0529525
Merge part 8 into part 9
varunj-msft Nov 14, 2026
d0bb066
Ask the backend whether a target's values can be saved
varunj-msft Nov 14, 2026
eef542d
Merge part 9 into part 10, keeping who saved a target in capability d…
varunj-msft Nov 14, 2026
3de68f9
Merge part 10 into part 11
varunj-msft Nov 14, 2026
f9d8ee6
Merge part 11 into part 12
varunj-msft Nov 14, 2026
83408f9
Expect the cause in the converter storage failure log
varunj-msft Nov 14, 2026
05fe656
Merge part 12 into part 13
varunj-msft Nov 14, 2026
a5f763e
Merge part 13 into part 14
varunj-msft Nov 14, 2026
84d0f9b
Type the saved recipe as a component or technique recipe
varunj-msft Nov 14, 2026
7423d77
Merge part 14 into part 15
varunj-msft Nov 14, 2026
f47ea4c
Merge part 15 into part 20
varunj-msft Nov 14, 2026
c05e125
Check saveability with a request that registers nothing
varunj-msft Nov 14, 2026
0d6cc7a
Merge part 9 into part 10
varunj-msft Nov 14, 2026
0310478
Merge part 10 into part 11
varunj-msft Nov 14, 2026
a2f1834
Merge part 11 into part 12
varunj-msft Nov 14, 2026
188ee22
Merge part 12 into part 13
varunj-msft Nov 14, 2026
b674b90
Merge part 13 into part 14
varunj-msft Nov 14, 2026
7fc4e7a
Merge part 14 into part 15
varunj-msft Nov 14, 2026
5e4f4b6
Merge part 19 into part 20
varunj-msft Nov 14, 2026
e47689c
Wrap the saveability note in the backend README
varunj-msft Nov 14, 2026
3a81a37
Merge part 10
varunj-msft Nov 14, 2026
b3af544
Merge part 11
varunj-msft Nov 14, 2026
fbaec87
Merge part 12
varunj-msft Nov 14, 2026
e4c28ca
Merge part 13
varunj-msft Nov 14, 2026
2021767
Merge part 14
varunj-msft Nov 14, 2026
61f529f
Merge part 15
varunj-msft Nov 14, 2026
46a4b89
Merge part 20
varunj-msft Nov 14, 2026
e6cfbd9
Answer the saveability check in the numeric controls end-to-end mocks
varunj-msft Nov 14, 2026
192552a
Merge part 10
varunj-msft Nov 14, 2026
bd98bb1
Merge part 11
varunj-msft Nov 14, 2026
941d5dc
Merge part 12
varunj-msft Nov 14, 2026
b3ef7f5
Merge part 13
varunj-msft Nov 14, 2026
ef9458d
Merge part 14
varunj-msft Nov 14, 2026
8feab9c
Merge part 15
varunj-msft Nov 14, 2026
4732d54
Merge part 20
varunj-msft Nov 14, 2026
87de386
Bound the URL scheme when redacting text
varunj-msft Nov 14, 2026
96361e5
Merge part 9
varunj-msft Nov 14, 2026
e1ab38a
Merge part 10
varunj-msft Nov 14, 2026
977ad50
Merge part 11
varunj-msft Nov 14, 2026
351419b
Merge part 12
varunj-msft Nov 14, 2026
edbc24a
Merge part 13
varunj-msft Nov 14, 2026
9d357d2
Merge part 14
varunj-msft Nov 14, 2026
c2f37db
Merge part 15
varunj-msft Nov 14, 2026
6a566da
Merge part 20
varunj-msft Nov 14, 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
9 changes: 6 additions & 3 deletions doc/getting_started/pyrit_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ A SAS query string may be included; otherwise, PyRIT uses `DefaultAzureCredentia

### `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. An Azure URI may include a blob-name prefix:
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
Expand All @@ -206,19 +206,22 @@ A SAS query string may be included; otherwise, PyRIT uses `DefaultAzureCredentia

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 saved file doesn't record 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.
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 and status 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; everyone else sees that an administrator has to fix saved entries. Fix or delete the listed files, or, 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`.
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`.

#### Saved entry requirements

A saved 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. Restore refuses a target file that breaks these rules:

- It can't read an empty key from an environment variable or leave an address to this server's settings. An address the server would otherwise fill in must be a full http(s) or ws(s) address.
- It sets each setting its target type would otherwise read from this server's environment, such as an OpenAI target's `model_name`, and only parameters the create API accepts, so no Python objects such as `httpx_client_kwargs`.
- 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.
- 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.

### `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 } : {}),
};
}
74 changes: 69 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,35 @@ 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,
},
],
}),
});
});
// Also registered after the broad route, so the dialog's save check isn't taken for a create.
await page.route(/\/api\/targets\/saveability$/, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ not_saveable_reason: null }),
});
});

await goToTargets(page);

Expand All @@ -453,11 +512,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 +560,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
1 change: 1 addition & 0 deletions frontend/e2e/numeric-controls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ async function mockNumericControlApis(page: Page): Promise<void> {
items: [makeTarget({ target_registry_name: 'mock-target' })],
pagination: { limit: 200, has_more: false },
},
'/api/targets/saveability': { not_saveable_reason: null },
'/api/targets/types': {
items: [
{
Expand Down
Loading
Loading