Skip to content

FIX: Stop a scan whose launch scripts would replace a saved entry (part 6) - #3986

Open
varunj-msft wants to merge 170 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Launch-Keeps-Saved-Entries
Open

varunj-msft wants to merge 170 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Launch-Keeps-Saved-Entries

Conversation

@varunj-msft

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

Copy link
Copy Markdown
Contributor

Description

Part 6 of saving Registry entries across restarts. Stacked on part 4 (#3909), not on part 5; part 7 (#4025) builds on it. This PR's own changes: varunj-msft/PyRIT@varunj-msft/13178-Restore-Saved-Entries...varunj-msft/13178-Launch-Keeps-Saved-Entries

Design: 4.6 When a saved entry can't be restored. Where every part fits: PR map.

Scripts a scan runs at launch could replace an entry restored from a saved recipe, for everyone using the app, or leave it in place so the scan ran with it instead of their own. This makes the registries refuse that and the launch stop with 409, naming the script and the entry. Until the earlier PRs merge, the diff here also shows their changes.

  • Restore marks each entry it rebuilds, in the entry's metadata (SAVED_ENTRY_METADATA_KEY, holding the kind).
  • DefaultInstanceRegistry.register(..., replace=True) refuses to replace a marked entry with SavedEntryReplacementError. It's a RuntimeError, because the converter and scorer startup scripts skip any entry that raises a ValueError.
  • register_from_factories (techniques), the tools script, the target script's round-robin grouping, and the scorer script's two picks by name (the preferred scorer in each category and scale_and_refusal) refuse instead of using a marked entry in place of their own.
  • The launch records refusals while each script runs, so one the script caught itself, or wrapped in an error of its own, still counts, and returns 409 naming the script and the entry. That includes the duplicate-name error for a marked entry, whether it comes from register, from the name check create_named_instance runs first, or from the technique registry's case-insensitive name check. A suite launch runs its scripts before it creates anything, so it gets the same 409 and message and starts no scan.

What stays the same: Startup and apply start from empty registries, run the scripts, then restore, so nothing is marked while the scripts run. Launches whose scripts don't touch a saved name, and scripts replacing startup entries, work as before. Creating entries through the API already refuses taken names.

Not covered: A custom script that changes registry contents without going through the registry methods. Picking a scorer by tag or by metrics still considers every registered scorer, saved ones included.

…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/middleware/error_handlers.py
#	pyrit/backend/routes/scenario_presets.py
#	pyrit/backend/routes/scenario_suites.py
#	pyrit/registry/file_document_storage.py
The runtime status keeps its short summary. The restore log now also lists each field's full location
and message, escaped, without the stored values, and with URL credentials removed. Credentials are
removed before a location is shortened, so the summary can't keep part of one either.
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed this PR's own layer over #3909 (18 files; +218/-13 production lines), separately from the inherited persistence stack.

I do not recommend replacing the refusal tracking with just an exception. A custom initializer can catch the ordinary ValueError raised by create_named_instance for a saved name, return successfully, and leave the next scan to resolve the wrong saved component. This can happen on a fresh launch; disabling interrupted-scan resume does not remove it. The bounded collector detects that handled refusal before run admission.

One remaining P2 issue is noted inline: suite preflight does not map that saved-entry conflict to 409. This is a focused layer review, not approval of the inherited stack.

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.

4 participants