Follow-up to PR #353 (security(roles): scope program/CR roles to Tier-3 registry read), which fixed Program Viewer and the CR roles but deliberately left Program Validator out of scope. Surfaced by the staff review of #353.
Severity: Low (consistency / defense-in-depth; UI enumeration surface)
Problem
spp_programs/data/user_roles.xml global_role_program_validator still implies Tier-2 spp_registry.group_registry_viewer, which gates the standalone Registry Search portal menu (spp_registry_search.menu_registry_search) — the same broad registrant-PII enumeration UI that #353 removed from Program Viewer and the CR roles.
Program Validator also already implies Tier-3 spp_registry.group_registry_write (which transitively grants group_registry_read), so it does not need group_registry_viewer for registrant read. The Tier-2 group only adds the Registry app menu. This is exactly the situation the sibling global_role_program_cycle_approver already avoids (it uses group_registry_write only, no viewer) and that #353 fixed for the other roles.
Lower severity than #353's roles because Program Validator holds registry write (a more privileged role, so the Registry app menu is more defensible), which is why it was scoped out — but for consistency and least-privilege it should match.
Suggested fix
In spp_programs/data/user_roles.xml, remove Command.link(ref('spp_registry.group_registry_viewer')) from global_role_program_validator (it keeps group_registry_write → read; only the menu drops). Add a post-migration mirroring PR #353's (unlink viewer from the role's implied_ids, then role.action_update_users() to re-sync assigned users) — the role ships noupdate="1" in the 2026.07 release. Version bump + HISTORY + regenerated README. Add a test asserting the role no longer confers group_registry_viewer but retains registrant read via group_registry_write.
Note: this touches spp_programs, which has multiple in-flight PRs bumping its version (#336, #353) — coordinate the version/HISTORY to avoid collision.
Origin
Follow-up to PR #353 (
security(roles): scope program/CR roles to Tier-3 registry read), which fixed Program Viewer and the CR roles but deliberately left Program Validator out of scope. Surfaced by the staff review of #353.Severity: Low (consistency / defense-in-depth; UI enumeration surface)
Problem
spp_programs/data/user_roles.xmlglobal_role_program_validatorstill implies Tier-2spp_registry.group_registry_viewer, which gates the standalone Registry Search portal menu (spp_registry_search.menu_registry_search) — the same broad registrant-PII enumeration UI that #353 removed from Program Viewer and the CR roles.Program Validator also already implies Tier-3
spp_registry.group_registry_write(which transitively grantsgroup_registry_read), so it does not needgroup_registry_viewerfor registrant read. The Tier-2 group only adds the Registry app menu. This is exactly the situation the siblingglobal_role_program_cycle_approveralready avoids (it usesgroup_registry_writeonly, no viewer) and that #353 fixed for the other roles.Lower severity than #353's roles because Program Validator holds registry write (a more privileged role, so the Registry app menu is more defensible), which is why it was scoped out — but for consistency and least-privilege it should match.
Suggested fix
In
spp_programs/data/user_roles.xml, removeCommand.link(ref('spp_registry.group_registry_viewer'))fromglobal_role_program_validator(it keepsgroup_registry_write→ read; only the menu drops). Add a post-migration mirroring PR #353's (unlink viewer from the role'simplied_ids, thenrole.action_update_users()to re-sync assigned users) — the role shipsnoupdate="1"in the2026.07release. Version bump + HISTORY + regenerated README. Add a test asserting the role no longer confersgroup_registry_viewerbut retains registrant read viagroup_registry_write.Note: this touches
spp_programs, which has multiple in-flight PRs bumping its version (#336, #353) — coordinate the version/HISTORY to avoid collision.Origin
internal/plans/sec-program-cr-registry-viewer-overgrant.md(residual deps(actions): Bump docker/build-push-action from 5 to 6 #1).