Skip to content

FEAT: Show who saved each target and list only your own (part 10) - #4028

Open
varunj-msft wants to merge 293 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Show-Who-Saved
Open

varunj-msft wants to merge 293 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-Show-Who-Saved

Conversation

@varunj-msft

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

Copy link
Copy Markdown
Contributor

Description

Part 10 of saving Registry entries across restarts. Stacked on part 9 (#4027); part 11 (#4050) builds on it. This PR's own changes: varunj-msft/PyRIT@varunj-msft/13178-Save-From-Registry...varunj-msft/13178-Show-Who-Saved

Design: 4.4 Credentials (Owners). Where every part fits: PR map.

This is one of the changes for saving Registry entries across restarts. It builds on #4027, which saves targets from the Registry. Until the earlier PRs merge, the diff here also shows their changes.

Problem. With sign-in on, everyone's saved targets land in one Targets list, and nothing says who saved which, so users can't find their own. Frederic raised this on the design gist and suggested Mine, Official and All views.

Change.

  • The server already records who saved a target in its saved file. It now keeps that on the running target, both when the target is saved and when it's restored at startup or apply. Restore does the same for converters and scorers, for when they can be saved.
  • Each listed target says who saved it (saved_by, the name) and whether that's the signed-in user asking (saved_by_me). The server compares the object IDs, so no ID leaves it.
  • The Targets table shows "Saved by you" or "Saved by " under the Saved label. An "Only targets I saved" checkbox sits under the "Show hidden targets" button. It shows only while you have a saved target, so with sign-in off nothing new appears, and like "Show hidden targets" it isn't kept across reloads.

What stays the same. Anyone can still save and use every target, and nothing here decides who can change or delete one. The saved file format doesn't change. Targets from configuration and Temporary targets have no owner. The round robin member list and the other target pickers are unchanged.

Not covered.

  • Who can change or delete a saved target (its owner or an admin). That comes with editing and deleting.
  • Saving converters and scorers. Those PRs set the owner the same way.
  • An Official view. Targets from configuration already have their own label; marking a saved target as official can come later.

Tests and Documentation

  • tests/unit/registry/test_registry_recipe_restore.py: restore keeps who saved each target, converter and scorer; an entry saved without sign-in has nobody.
  • tests/unit/backend/test_target_service.py: a saved target says who saved it and whether it was the caller, for the saver, another user and nobody signed in, the same before and after a restart; a target saved with sign-in off and a Temporary one name nobody, including the create response.
  • tests/unit/backend/test_mappers.py: the name and the flag for each owner and caller, a blank name, no object ID in the response, and inner targets of a saved round robin naming nobody.
  • tests/unit/backend/test_api_routes.py and test_auth_middleware.py: list and get pass the caller's object ID, and none with sign-in off.
  • TargetTable.test.tsx: the captions; the checkbox narrowing the list, together with the column filters and with hidden targets; no checkbox when nothing is yours; turning off when your last saved target is gone.
  • frontend/e2e/config.spec.ts (mock): the captions, the checkbox, and a fresh load listing everyone again. frontend/e2e/registry.spec.ts (seeded, sign-in off): nobody is named and there's no checkbox.
  • Each of the 18 ways I tried to break this fails at least one of these tests.
  • 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,612 passed, 9 skipped, 4 xfailed
  • cd frontend && npm run test:coverage: 184 suites, 4,563 tests passed
  • cd frontend && npx playwright test --project=mock e2e/config.spec.ts e2e/registry.spec.ts: 29 passed
  • cd frontend && E2E_SEEDED_MODE=true npx playwright test --project=seeded: 109 passed
  • npx tsc --noEmit, npx eslint on the changed files, and pre-commit (ruff, ty, docs checks): passed
  • doc/gui/0_gui.md (Registry): the "Saved by" caption and the checkbox. pyrit/backend/README.md: the two new fields.

No JupyText changes.

The banner tied a retry to its failure only through the failed state, so a
retry from an earlier failure could still show its answer when the polls
missed the runtime becoming ready in between. The generation changes only
when the runtime becomes ready or the backend restarts, so a new one now
starts a new failure too.
A long failure list made the banner taller than a phone screen, and the app
doesn't scroll, so Retry could end up out of reach. The list now scrolls on
its own within a third of the screen, and can be scrolled from the keyboard.
When the list can't be loaded, the banner says so, keeps the last list it had,
and keeps trying.
Scripts a scan runs at launch register their targets, converters and scorers
with replace, so one that used a saved entry's name silently replaced it for
everyone, and scripts that skip names already taken (the technique and tools
scripts, round-robin grouping, the scorer script's preferred scorers) left the
scan with the saved entry instead of their own. Restore now marks the entries
it rebuilds, the registries and those scripts refuse to replace or stand in for
a marked entry, and the launch stops with a 409 naming the script and the entry,
even when the script caught the refusal itself. A suite shows the same message
for that entry.
A launch script that caught the refusal and raised its own error skipped the
recorded-refusal check, so the launch failed with that error instead of the
409 naming the saved entry. The clash is now reported on any error once a
refusal was recorded; other errors pass through as before.
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.
# Conflicts:
#	pyrit/registry/registry_recipe_storage.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