Repository navigation
FEAT: Restore saved registry entries and stay not ready if any fail - #3909
Open
varunj-msft wants to merge 64 commits into
Open
varunj-msft wants to merge 64 commits into
varunj-msft wants to merge 64 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
…reports an unknown attack type
…s that can't be checked
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Offloading the entire restore runs loop-dependent constructors without an active event loop, causing valid saved HuggingFace model recipes to fail and block startup or apply.
1 open issue
What changed in this PR
Adds restoration of saved registry entries to PyRIT’s backend startup and live configuration apply, building on the dependency-aware restoration in #3908. Failed restores keep the runtime unavailable rather than silently omitting required components.
Changes:
- Adds recipe models, local/Azure Blob storage, and dependency-ordered restoration.
- Integrates restoration with runtime readiness and exposes failures and ignored files.
- Documents storage configuration and adds tests with isolated recipe folders.
| File | Description |
|---|---|
tests/unit/setup/test_configuration_loader.py |
Tests recipe-source configuration and validation. |
tests/unit/registry/test_registry_recipe_storage.py |
Tests recipe parsing, storage, and report serialization. |
tests/unit/registry/test_registry_recipe_restore.py |
Tests dependency ordering, validation, and restore failures. |
tests/unit/registry/test_file_document_storage.py |
Tests local and Blob listing diagnostics and file races. |
tests/unit/backend/test_runtime_lifecycle.py |
Tests restoration, readiness, recovery, and cancellation. |
tests/unit/backend/test_analytics_benchmark.py |
Verifies benchmark recipe-folder isolation. |
tests/unit/backend/conftest.py |
Isolates saved entries for backend unit tests. |
test_support/analytics_benchmark.py |
Configures a benchmark-specific recipe folder. |
pyrit/setup/configuration_loader.py |
Adds and validates registry_recipes_source. |
pyrit/registry/registry_recipe_storage.py |
Reads and validates versioned registry recipes. |
pyrit/registry/registry_recipe_restore.py |
Rebuilds entries in dependency order and reports failures. |
pyrit/registry/file_document_storage.py |
Adds listings that report unreadable and ignored documents. |
pyrit/models/request_limits.py |
Adds a shared text-length limit. |
pyrit/models/catalog/registry_recipe.py |
Defines recipe and restore-report models. |
pyrit/models/catalog/__init__.py |
Lazily exports the new catalog models. |
pyrit/backend/services/runtime_lifecycle.py |
Restores entries before publishing readiness. |
pyrit/backend/README.md |
Documents offline browser-fixture isolation. |
frontend/e2e/fixtures/manual_send_backend.py |
Uses a temporary recipe folder. |
doc/getting_started/pyrit_conf.md |
Documents recipe storage and failure recovery. |
.pyrit_conf_example |
Adds an example recipe-storage setting. |
Review effort: Balanced
| bool: Whether every saved entry was restored. | ||
| """ | ||
| restorer = RegistryRecipeRestorer(source=config.registry_recipes_source) | ||
| report = await asyncio.to_thread(restorer.restore) |
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's the last of four stacked PRs and builds on #3908. Until the earlier PRs merge, the diff here also shows their changes.
Problem. Saved entries need to come back at startup and after a live apply. A broken one must stop runs instead of vanishing; otherwise a scan that expects a saved converter or
adversarial_chatwould run with something else, or fail later.Change.
registry_recipes_source: a local folder or Azure Blob location, default~/.pyrit/registry_recipes. It's validated likescenario_presets_sourceand documented inpyrit_conf.mdand.pyrit_conf_example.RuntimeLifecyclerestores saved entries in two places. At startup, after the initializers run and the configuration version is read. On apply, after the new version is recorded; apply already resets the registries, so restore starts clean. The recipes are read in a worker thread, and the entries are built on the runtime loop, as the target API builds them, since a constructor such asHuggingFaceChatTarget's starts a task on that loop. As with the target API, a slow constructor holds the loop while it runs.failedwith outcomerestore-failedand doesn't publish. The middleware already returns 503 for runtime routes, and/api/healthshowsready: false.GET /api/config/runtimelistsrestore_failuresandrestore_ignored, and the failures are logged. For now the fix is to correct or delete the file and restart.What stays the same. With no saved entries, startup and apply behave as before. A file whose name isn't a valid entry name is listed as ignored and doesn't block readiness.
Next. Fixing or deleting a broken entry without a restart, and showing the failure list to every signed-in user rather than only in the admin runtime status, come with the recovery routes in the next PR.
Tests and Documentation
tests/unit/backend/test_runtime_lifecycle.py: startup and apply restore an entry that uses a target the startup scripts register, the recipes are read in a worker thread and restored entries are built on the runtime loop, startup and apply with a broken entry stay not ready and list it in the runtime status, ignored files don't block readiness, fixing the file and restarting reaches ready, cancellation still propagates, and a reference name or file name that isn't valid text still returns 200.tests/unit/setup/test_configuration_loader.pycovers the new setting;tests/unit/backend/conftest.py, the analytics benchmark fixture and the offline browser fixture (frontend/e2e/fixtures/manual_send_backend.py) point the recipes folder at a temp folder, so they never read a developer's saved entries.pytest tests/unit/backend tests/unit/setup tests/unit/registry tests/unit/models -n 4: 8,053 passed, 9 skipped, 4 xfailedpre-commit run --from-ref origin/main --to-ref HEAD: passed/api/healthshowedready: false,GET /api/config/runtimereturned 200 with all three failures and the escaped file name, and neither the status nor the log held the test secret. After deleting the three broken files and restarting, it reached ready, with the oddly named file still listed as ignored.HuggingFaceChatTarget(offline, so only its background model load fails): it reached ready at startup and after a live apply. With the restore in a worker thread instead, startup failed withno running event loop.doc/getting_started/pyrit_conf.mdand.pyrit_conf_exampledescriberegistry_recipes_source. JupyText: not run (no notebooks changed).