security(cr): validate program access on assign-program detail#338
Draft
gonzalesedwin1123 wants to merge 5 commits into
Draft
security(cr): validate program access on assign-program detail#338gonzalesedwin1123 wants to merge 5 commits into
gonzalesedwin1123 wants to merge 5 commits into
Conversation
The spp.cr.detail.assign_program program_id domain only constrains the UI, so
a CR user could RPC-write an arbitrary program id. The apply strategy runs on
self.sudo(), so program record rules and multi-company scope were bypassed at
validation, membership creation, and preview - allowing assignment to a
hidden/cross-company program and leaking its name.
Add @api.constrains('program_id') on the detail that, in the writing user's own
context, rejects a program the user cannot see (record rules) or that is outside
their company scope (explicit env.companies check, since the global company
ir.rule is not reliably applied to searches in the write context).
Bump 19.0.1.0.1 -> 19.0.1.0.3 (dodging #261's 19.0.1.0.2 on the same module) +
HISTORY. No migration: module is not in any released tag.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 19.0 #338 +/- ##
==========================================
- Coverage 74.28% 68.10% -6.18%
==========================================
Files 372 101 -271
Lines 25385 8422 -16963
==========================================
- Hits 18857 5736 -13121
+ Misses 6528 2686 -3842
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…angelog Applies CI's pinned oca-gen output verbatim for the new HISTORY fragment.
The keep-a-changelog '## <version> (date)' / '### Fixed' fragment made the oca-gen README generator crash (docutils 'Inconsistent title style: skip from level 2 to 4'). Switch to OCA flat '### <version>' entries (matching the module's DESCRIPTION heading ladder). README.rst reverted to base so CI's pinned generator regenerates it cleanly.
…TORY Applies CI's pinned oca-gen output verbatim for the OCA-style changelog.
- Correct the constraint docstring: the explicit company check is load-bearing, not defense-in-depth. Verified by experiment that the global multi-company ir.rule on spp.program is NOT applied to the search in this write/constraint context, so search-only would let a cross-company program through; drop the inaccurate 'area/registrant record rules' claim (spp.program has only the company rule). - Document in the apply strategy that program access is enforced at detail write time, so any future sudo writer of program_id must re-check. - Make the tests robust to in-flight PR #261's detail-ownership rule by using the cr_user's own partner as the CR registrant (matches #261's registrant branch); add a shared-program (company_id=False) allow test. Tests: 23 pass (was 22), run as a non-admin group_cr_user.
Member
Author
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
spp_cr_type_assign_programapply strategy validated a selected program'sexistence,
state, target type, and uniqueness — but not the user's accessto the program or its company. The detail's
program_iddomainonlyconstrains the UI, so an authenticated CR user with detail write access could
submit a raw ORM/RPC
write({'program_id': <arbitrary id>}). On apply, core CRcode runs the strategy on
self.sudo()(spp.change.request._do_apply), soprogram record rules and multi-company scope were bypassed during validation and
membership creation — assigning the registrant to a hidden/cross-company
program — and preview generation leaked that program's display name through the
same sudo path.
Fix
Add
@api.constrains('program_id')tospp.cr.detail.assign_programthat, inthe writing user's own context, rejects a program they cannot access:
search()must return the program (enforces any recordrule on
spp.program, and rejects a stale/deleted id);company_idmust be withinself.env.companies. This explicit check is load-bearing, not justdefense-in-depth: verified by experiment that the global multi-company
ir.ruleonspp.programis not applied to the search in thiswrite/constraint context, so
searchalone would let a cross-company programthrough.
This guards the only sender-reachable input path to
program_id; the sudoapply/preview then only ever consume an already-validated, accessible program.
The apply strategy is annotated to make explicit that access is enforced at
detail-write time, so any future code that sets
program_idunder sudo mustperform its own access check.
Design decision (confirmed with owner)
Constraint-only (no extra check inside the sudo
validate()): the constraintblocks storing an inaccessible
program_idat the source, and the module isunreleased so no pre-existing poisoned records exist.
Testing
./spp t spp_cr_type_assign_program→ 23 passed, 0 failed, 0 errors (19existing + 4 new), run as a non-admin
group_cr_user.detail pointing at a company-B program; is allowed to select a company-A
program; and is allowed to select a company-shared (
company_id = False)program.
Staff review
Two independent adversarial staff reviews were run. Verdict: ship-with-nits, no
open security hole. Findings addressed in-branch:
proven by removing it and watching the reject tests fail).
user's own partner as registrant) and added the shared-program allow case.
Consciously left (low, non-security): an archived program is reported as "no
access" rather than "archived" (apply re-validates
stateregardless); and auser lacking any programs-read group gets
AccessErrorrather than the friendlyValidationError(pre-existing — the feature already requires a programs group).Notes for reviewers
cr-detail-record-rules) also touches thismodule — it adds parent-CR-ownership/area record rules to the detail
(which detail records you may write). This PR is complementary: it governs
which program you may target on a detail you legitimately own. Both bump
the manifest/HISTORY/README, so a version/changelog collision is expected;
this PR uses
19.0.1.0.3(higher than both the19.0base19.0.1.0.1andsecurity(cr): add record rules to CR detail models (ownership + area) #261's bump), so whichever merges second needs a trivial re-merge.
spp_cr_type_assign_programis not in any released tag.Draft: awaiting human security review before undraft/merge.
Merge order (CR security cluster)
Per the cross-PR interaction analysis (2026-07-24,
internal/plans/security-prs-interaction-analysis.md), hard order: #264 → #261 → #338 (this PR) → #343.spp_programs.group_programs_viewertouser_a,user_b, andvalidatorinspp_cr_type_assign_program/tests/test_detail_security.py. Reason: this PR's_check_program_accessconstraint runs aspp.programsearch()in the writing user's context, and security(cr): add record rules to CR detail models (ownership + area) #261's fixture users hold onlygroup_cr_user, which grants nospp.programread ACL — without the fix, 4 of 5 tests in that file fail on the combined tree. This order makes the failure (and the fix) surface in this PR's own CI.program_idremains repointable post-submit; approval-fidelity gap, fix via security(cr): route and apply the same single field for dynamic approvals #264's_protected_content_fields).