Skip to content

FEAT: Save converters and scorers created through the API (part 12) - #4051

Open
varunj-msft wants to merge 341 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Save-Converters-Scorers
Open

varunj-msft wants to merge 341 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Save-Converters-Scorers

Conversation

@varunj-msft

@varunj-msft varunj-msft commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

Part 12 of saving Registry entries across restarts. Stacked on part 11 (#4050); part 13 (#4052) builds on it. This PR's own changes: varunj-msft/PyRIT@varunj-msft/13178-Refuse-Saved-Converter-Scorer-Credentials...varunj-msft/13178-Save-Converters-Scorers

Design: 4.1 What gets saved, 4.5 Entries that use other entries. Where every part fits: PR map.

This is one of the changes for saving Registry entries across restarts. It builds on #4050, which refuses saved converters and scorers that would use this server's credentials. Until the earlier PRs merge, the diff here also shows their changes.

A converter or scorer created through the API is gone when the backend restarts. With "persist": true, POST /api/converters and POST /api/scorers now save it the way POST /api/targets does (#4026), so restore brings it back at every start.

  • The first commit only moves code. The target save steps go from target_service.py to a shared services/saved_entries.py: turn off the server values an entry would read, refuse what it can't hold or a Temporary entry it uses, write the recipe create-only, register it, and delete the recipe again if registering fails. The owner and not-saveable helpers move to routes/common.py, and TargetOrigin (new in FEAT: Save targets created through the API (part 8) #4026) becomes RegistryEntryOrigin. Targets behave as before.
  • persist on converters and scorers follows the target rules. It needs register and a name matching ^[a-z][a-z0-9_]{0,63}$ (422 otherwise). A refusal is a typed 400 (/errors/not-saveable), a taken name is 409, and unreadable storage is 503. The owner is recorded as for targets.
  • A saved converter or scorer may only use saved or configuration entries. A reference left out of the request but filled from the configured default values (@apply_defaults) is written into the recipe by name, each entry of a list included, so restore uses the same entries even if nothing sets that default at the next start. A default that is Temporary, or isn't a registered entry, is refused. Other configured defaults apply again from the configuration at every start, as they do for startup entries.
  • A converter to save can't take an uploaded file yet, and is built without file parameters, as restore builds it. A type that needs a file says so. A copy made with source can't be saved yet (422).
  • Saving a scorer finds its class and builds it off the event loop, since finding the class can take over a second.
  • Converters and scorers now have origin (saved, configuration or temporary) on list, get and create, and the startup entries of all three kinds are marked as configuration.
  • DELETE /api/converters/{id} returns 409 for a saved converter, since it would come back from its file at the next start. Temporary and configuration converters are removed as before.
  • The scorer name types is now reserved, as it is for targets and converters, since GET /api/scorers/types would hide a scorer with that name.
  • The backend README and pyrit_conf.md describe saving converters and scorers.

Not covered:

  • Converters that pick randomly without a seed, such as the bijection and policy puppetry ones, come back with a new mapping after a restart, as startup converters do.
  • GET /api/scorers/types doesn't say yet why a scorer type can't be saved; saving one refuses it with the reason.
  • Saving converters from the GUI is FEAT: Save converters from the Registry (part 13) #4052 (part 13). Showing who saved a converter or scorer, saving copies, deleting or editing saved entries, and keeping uploaded or Blob files on saved converters come later.

The dialog asks for a registry name and suggests one, saves the target by
default, and creates a Temporary target only when it can't be saved, saying
why before it is created. The Targets table labels each target Saved, From
configuration or Temporary.
# Conflicts:
#	pyrit/registry/registry_recipe_restore.py
# Conflicts:
#	pyrit/registry/registry_recipe_restore.py
Its text can carry a SAS URL. The 503 handler no longer logs it as a debug traceback, and the scenario
suite service no longer attaches it to its warning; both log the storage message, which has no credentials.
# Conflicts:
#	frontend/src/components/Config/CreateTargetDialog.tsx
A suite launch runs its launch scripts during preflight and sends its scans without them, so the clash is
raised there. Map it to 409 with its message, as a single launch does, and drop the per-entry message that
a clash can no longer reach.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants