Found in the adversarial review of PR #536 (#443 work). Pre-existing since 19.0.3.1.10.
_assert_content_editable runs from write() only; create() on the detail models is unguarded, and group_cr_user has create rights on them (ir.model.access.csv). _alters_frozen_field (models/change_request.py ~706-720) accepts binding detail_res_id to any row that already points back at the request. So on a submitted request that has no detail row, a user can create() a fully populated detail with change_request_id = <cr> (values of their choosing, bypassing the freeze entirely) and then bind it through the 19.0.3.1.10 rule.
The legitimate repair path (_ensure_detail, after #536: create with the registrant prefill, under sudo) needs exactly one of these creates; user-driven creates pointing at a non-draft request do not.
Suggested fix: in the detail base create(), refuse a row whose change_request_id is not in draft/revision unless the values are the registrant prefill (or route the repair through a sudo-only helper and refuse all user creates against submitted requests), plus a test that crafts and binds such a row and asserts the refusal. Consider also making _alters_frozen_field accept a first binding only for a row created by the system (e.g. create_uid is OdooBot / created within _ensure_detail).
Related: #321 (registrant_id not frozen post-submit); #443.
Found in the adversarial review of PR #536 (#443 work). Pre-existing since 19.0.3.1.10.
_assert_content_editableruns fromwrite()only;create()on the detail models is unguarded, andgroup_cr_userhas create rights on them (ir.model.access.csv)._alters_frozen_field(models/change_request.py~706-720) accepts bindingdetail_res_idto any row that already points back at the request. So on a submitted request that has no detail row, a user cancreate()a fully populated detail withchange_request_id = <cr>(values of their choosing, bypassing the freeze entirely) and then bind it through the 19.0.3.1.10 rule.The legitimate repair path (
_ensure_detail, after #536: create with the registrant prefill, under sudo) needs exactly one of these creates; user-driven creates pointing at a non-draft request do not.Suggested fix: in the detail base
create(), refuse a row whosechange_request_idis not in draft/revision unless the values are the registrant prefill (or route the repair through a sudo-only helper and refuse all user creates against submitted requests), plus a test that crafts and binds such a row and asserts the refusal. Consider also making_alters_frozen_fieldaccept a first binding only for a row created by the system (e.g.create_uidis OdooBot / created within_ensure_detail).Related: #321 (registrant_id not frozen post-submit); #443.