Repository navigation
FEAT: Save targets from the Registry - #4027
Open
varunj-msft wants to merge 140 commits into
Open
varunj-msft wants to merge 140 commits into
varunj-msft wants to merge 140 commits into
Conversation
…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
…benchmark's recipes folder
# Conflicts: # pyrit/registry/registry_recipe_restore.py
varunj-msft
requested review from
hannahwestra25 and
Richard Lundeen (richlundeen)
November 7, 2026 01:51
# Conflicts: # tests/unit/registry/test_instance_registry.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
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.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This is one of the changes for saving Registry entries across restarts. It builds on #4026, which saves targets created through the API. Until the earlier PRs merge, the diff here also shows their changes.
Problem. The New Target dialog in Registry > Targets still makes targets that are gone after the backend restarts. It sends no name, so the backend makes one up (
compat_<hex>), and the Targets table doesn't say which targets will come back.Change.
openai_chat_gpt_4o). The model tells saved targets apart and keeps the suggestion off names the startup scripts register, such asopenai_chat. The field checks the same rule the backend uses for saved names. A name the Targets list already has, or one the backend says is taken (409), shows on the field and holds Save until the name changes.persist: true). When a target can't be saved, the button reads Create as Temporary and a warning says why, and that a Temporary target is gone after the backend restarts or is reinitialized. The dialog says so before submitting when the backend's type list already tells it: the type can't be saved, the chosen sign-in can't be saved (an API key, for now), a credential is typed in, or a round robin uses a Temporary target. Otherwise the backend's refusal turns the button into Create as Temporary for those exact values, and changing any of them goes back to Save Target. No other error offers Temporary.GET /api/targets/typesaddssaveable_auth_modesandnot_saveable_reasonto each type. They come from the checks a save runs (the class check, and the server-setting check for each auth mode with those settings turned off), so the dialog keeps no copy of the credential rules."type": "/errors/not-saveable"), so the dialog doesn't read messages. When the only problem is an endpoint that isn't a full http(s) or ws(s) address, the request gets a plain 400 instead, since a Temporary target with that address wouldn't work either.role="status"so screen readers read them out. Fluent's message bars only announce through an announcer the app doesn't render.What stays the same. API callers that don't send
persistget Temporary targets as before, and may still leave out the name. Permission checks still come first, and restore is unchanged.Not covered.
Tests and Documentation
tests/unit/registry/test_registry_recipe_rules.py:check_saveable_classlists the auth modes a saved target of each type can use, refuses a class that can never be saved, and agrees with the check a save runs for every registered target type and auth mode.tests/unit/backend/test_target_service.py: the type list says which auth modes a saved target can use and why a type can't be saved; saving a type that can't be saved is refused; an endpoint that isn't a full web address is an error to fix, not a refusal.tests/unit/backend/test_api_routes.py: a refused save is a 400 with"type": "/errors/not-saveable"; other bad requests stay untyped.CreateTargetDialog.test.tsx(new "CreateTargetDialog saving" block): saving by default with Entra ID or No key, also when the type details arrive late; the suggested name from the type and model until it's edited; names already listed or taken (409) held on the field; when Create as Temporary is offered and why (an API key, a typed credential, a type that can't be saved, a round robin with a Temporary member), and only after a typed refusal for those exact values; no Temporary offer on 409, 422, 403 or other errors; the warning and the Entra ID error as live regions; starting over after the dialog closes; the Entra ID error once the endpoint field is left.TargetTable.test.tsx: the Saved, From configuration and Temporary labels.registryName.test.tsanderrors.test.ts: the name suggestion and the typed refusal check.frontend/e2e/config.spec.ts(mock): saving through the dialog sends Entra ID andpersist: true.frontend/e2e/registry.spec.ts(seeded, real backend): a target saved from the dialog is still Saved after a reload.uv run --frozen --extra all python -m pytest -n 8 --dist=loadfile tests/unit/registry tests/unit/backend tests/unit/prompt_target tests/unit/models tests/unit/setup tests/unit/converter tests/unit/score: 19,599 passed, 9 skipped, 4 xfailedcd frontend && npm run test:coverage: 184 suites, 4,557 tests passedcd frontend && npx playwright test --project=mock e2e/config.spec.ts e2e/registry.spec.ts: 28 passedcd frontend && E2E_SEEDED_MODE=true npx playwright test --project=seeded: 109 passednpx tsc --noEmit,npx eslinton the changed files, and pre-commit (ruff, ty, docs checks): passedpersistcame out Temporary, with the reason shown. After a backend restart the saved targets came back as Saved, the Temporary ones were gone, and a message sent through the restored Entra ID target got an answer. No key was in any saved file.doc/gui/0_gui.md(Registry): saving a target, where saved targets are kept, how to remove one for now, and what happens when a startup script registers the same name.pyrit/backend/README.md: the new type fields and the typed 400.No JupyText changes.