FIX: pyrit_shell run fails for every scenario with a flag-collision error - #2303
FIX: pyrit_shell run fails for every scenario with a flag-collision error#2303jsong468 wants to merge 1 commit into
pyrit_shell run fails for every scenario with a flag-collision error#2303Conversation
| A scenario's ``supported_parameters`` (fetched from the API) include the framework's common | ||
| parameters — e.g. ``memory_labels``, ``max_concurrency``, ``max_retries`` — which normalize to | ||
| flags already provided as built-ins. Those built-ins own the flag, so the scenario copy is | ||
| silently dropped, mirroring ``pyrit_scan._add_scenario_params_from_api``. A genuine |
There was a problem hiding this comment.
Rich agrees with this comment but it is copilot generated:
The docstring says this drop mirrors pyrit_scan._add_scenario_params_from_api, but it only mirrors the built-in collision case. _add_scenario_params_from_api uses a single seen_flags set and silently drops any duplicate flag (first-wins), including scenario-vs-scenario. Here we instead raise on a scenario-vs-scenario collision. Net effect: a scenario definition that runs fine under pyrit_scan would hard-fail under pyrit_shell — the two entrypoints aren't actually at parity for that case.
In practice the raise branch is close to dead code (param names are Python identifiers and the _→- normalization is injective over identifiers, so two distinct names can't collide), so severity is low. But it's worth either aligning with pyrit_scan (silently drop, first-wins) for true parity, or softening the "mirroring" wording to call out the intentional divergence and why.
Bug Description
Running any scenario from
pyrit_shellfailed before argument parsing with:Root cause: The shell registers each of a scenario's parameters as a CLI flag, using the full list from the API (
scenario_meta.supported_parameters). That list combines the scenario's custom parameters with the framework's common parameters (memory_labels,max_concurrency,max_retries), which normalize to flags that already exist as built-in shell flags. The collision guard_validate_scenario_flag_collisionsraised on the first such clash (memory_labels) instead of skipping it. Since every scenario carries the common parameters,runwas broken universally.pyrit_scanwas unaffected: its_add_scenario_params_from_apisilently skips any scenario flag that already exists as a built-in. The inconsistency between the two entry points was the bug.Fix
Make the shell mirror
pyrit_scan. Replaced_validate_scenario_flag_collisionswith_resolve_scenario_flag_collisionsinpyrit/cli/_cli_args.py, which:parse_run_argumentsnow uses the returned filtered specs rather than relying on a validator side effect.Testing
tests/unit/cli/test_cli_args.py::test_parse_run_arguments_skips_scenario_params_colliding_with_builtins— reproduces theairt.scamcase (common params present) and assertsrunparses successfully while the scenario's custom--max-turnsflag still works.tests/unit/cli/test_cli_args.py::test_parse_run_arguments_raises_on_scenario_vs_scenario_collision— confirms the scenario-vs-scenario check is preserved.test_pyrit_shell.py+test_pyrit_scan.pysuites pass (168/168), no regressions.