Skip to content

FEAT: Save converters from the Registry - #4052

Open
varunj-msft wants to merge 164 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Save-Converters-From-Registry
Open

varunj-msft wants to merge 164 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Save-Converters-From-Registry

Conversation

@varunj-msft

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

Copy link
Copy Markdown
Contributor

Description

Stacked on #4051. This PR's own changes: varunj-msft/PyRIT@varunj-msft/13178-Save-Converters-Scorers...varunj-msft/13178-Save-Converters-From-Registry

This is one of the changes for saving Registry entries across restarts. It builds on #4051, which saves converters and scorers created through the API. Until the earlier PRs merge, the diff here also shows their changes.

The New Converter dialog only made converters that are gone after the backend restarts, wherever it opens: the Converters tab, the converter panel in chat, and New converter in the technique editor. It now saves them by default, and the Converters and Scorers tables say which entries come back.

  • The dialog suggests a name from the type that no listed converter has, such as base64_converter. Keeping the _converter ending stays clear of the names the startup scripts register (base64, rot13), which would stop a saved converter of that name from restoring. The name field checks the backend's rule for saved names. A name the list already has, or one the backend says is taken (409), shows on the field, and focus moves there.
  • When a converter can't be saved, the button reads Create as Temporary and a warning says why: the type can't be saved, a file is uploaded, the chosen target is Temporary, or the backend refused those exact values (typed 400). Changing a refused value goes back to Save Converter. A file removed from a new converter is no longer sent. The warning uses role="status" so screen readers read it out, and target options mark Temporary targets.
  • GET /api/converters/types adds not_saveable_reason to each type, from the checks a save runs, including the one for types that need an uploaded file. The dialog keeps no copy of those rules.
  • The Converters table labels each converter Saved, From configuration or Temporary. A saved converter's Remove button is disabled, with a tooltip saying how to remove it for now. The Scorers table shows the same labels.
  • Save to registry on a converter's changed settings still makes a Temporary copy, and its dialog now says so.
  • The origin label moves from Config/TargetOriginBadge.tsx and utils/targetOrigin.ts to Registry/OriginBadge.tsx and utils/registryOrigin.ts. The target files change imports only.
  • The GUI doc covers saving converters and removing a saved one for now.

Screenshots: a new converter saves by default, a converter that uses a Temporary target is offered as Temporary with the reason, and the Converters table before and after a restart.
1_new_converter_saves_by_default
2_temporary_target_offers_temporary
4_converters_table_before_restart
5_converters_table_after_restart

Not covered:

  • Removing a saved converter in the GUI comes with deleting saved entries, in a later PR. Until then a converter saved from the dialog stays until its file is deleted and the backend restarted, as the tooltip and the GUI doc say.
  • Showing who saved each converter or scorer, with a filter for your own, is the next PR. Saving scorers and techniques from the GUI, saving copies, and editing saved converters come later.

…API input

External construction now rejects a parameter whose type includes Path unless the
calling service passes the name in allowed_path_params after making the value safe.
The converter service allows its upload parameters once they are stored, and the
target service allows the server-set upload folder and the GitHubCopilotTarget
working directory. The scorer-only check is removed, and the "Valid parameters"
list leaves out path parameters callers cannot set.
The target catalog now applies the path rule to required parameters too, so a
target that needs a disallowed path is left out instead of listed without the
field, matching the scorer catalog. allowed_path_params is typed frozenset[str]
so a bare string cannot turn the allow-list into a substring match.
The target catalog still lists a target whose required path parameter is its
server-set upload folder when the server has one, and leaves it out otherwise.
# Conflicts:
#	tests/unit/backend/test_converter_service.py
# Conflicts:
#	pyrit/backend/services/runtime_lifecycle.py
#	tests/unit/backend/test_runtime_lifecycle.py
# Conflicts:
#	pyrit/setup/initializers/tools.py
#	tests/unit/backend/test_scenario_run_service.py
#	tests/unit/backend/test_scenario_suite_execution_service.py
#	tests/unit/registry/test_attack_technique_registry.py
#	tests/unit/registry/test_instance_registry.py
#	tests/unit/registry/test_registry_recipe_restore.py
create_named_instance checks the name before it builds, and the technique
registry checks names case-insensitively before it registers. Both raised the
duplicate-name error without recording the clash, so a launch script that
caught either one could still leave a saved entry in place of its own. The name
check now records the clash itself, as register already did, and the technique
check records it too.
# Conflicts:
#	tests/unit/registry/test_registry_recipe_restore.py
# Conflicts:
#	pyrit/backend/mappers/target_mappers.py
#	pyrit/backend/models/targets.py
#	pyrit/backend/routes/targets.py
#	pyrit/backend/services/target_service.py
#	tests/unit/backend/test_api_routes.py
#	tests/unit/backend/test_mappers.py
#	tests/unit/backend/test_runtime_lifecycle.py
#	tests/unit/backend/test_target_service.py
#	tests/unit/registry/test_instance_registry.py
# Conflicts:
#	frontend/src/components/Config/CreateTargetDialog.tsx
#	pyrit/backend/routes/targets.py
#	pyrit/backend/services/target_service.py
#	tests/unit/backend/test_target_service.py
#	tests/unit/registry/test_registry_recipe_rules.py
# Conflicts:
#	pyrit/backend/mappers/target_mappers.py
#	pyrit/backend/routes/targets.py
#	tests/unit/backend/test_api_routes.py
#	tests/unit/backend/test_mappers.py
#	tests/unit/registry/test_registry_recipe_restore.py
# Conflicts:
#	tests/unit/registry/test_registry_recipe_rules.py
# Conflicts:
#	pyrit/backend/mappers/target_mappers.py
#	pyrit/backend/models/scorers.py
#	pyrit/backend/routes/converters.py
#	pyrit/backend/routes/targets.py
#	pyrit/backend/services/converter_service.py
#	pyrit/backend/services/scorer_service.py
#	pyrit/backend/services/target_service.py
#	pyrit/registry/instance_registry.py
#	tests/unit/backend/test_api_routes.py
#	tests/unit/backend/test_converter_service.py
#	tests/unit/backend/test_mappers.py
#	tests/unit/backend/test_runtime_lifecycle.py
#	tests/unit/backend/test_scorer_service.py
#	tests/unit/backend/test_target_service.py
#	tests/unit/registry/test_instance_registry.py
main now declares that TextTarget takes no credentials, with an empty
supported_auth_modes. The saved-entry check read that as supporting no mode at
all, so it refused saved TextTargets and left the saveability check for the
target types with no mode to report. A class that declares no auth modes is now
checked as the default api_key mode, which it has no key for.
# Conflicts:
#	pyrit/registry/registry_recipe_rules.py
A reference left to a configured default can be a list, such as a composite
scorer's scorers. Each entry in the list is now named in the saved recipe, in
order, and the list is refused if any of them is Temporary or not registered,
instead of the whole list being refused as one unregistered entry.
# Conflicts:
#	frontend/src/components/Registry/CreateConverterDialog.tsx

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.

2 participants