Skip to content

security(spp_grm): portal users can read/write ALL grievance tickets over RPC — no portal record rule [Severity: High] #413

Description

@gonzalesedwin1123

Discovered during the verification of PR #399's review findings (spp_grm_cel rule-ACL analysis). Filed as its own issue because it is a live gap on released 19.0 and is strictly broader than the rule-model concerns raised there.

Severity: High (portal-authenticated read/write of all grievances)

Problem

spp_grm/security/ir.model.access.csv grants base.group_portal read, write and create on spp.grm.ticket:

access_spp_grm_ticket_portal_user,GRM Ticket Portal User Access,model_spp_grm_ticket,base.group_portal,1,1,1,0

and no ir.rule applies to portal users at all: spp_grm/security/rules.xml defines record rules only for the staff groups (group_grm_viewer/officer/supervisor/manager), and there is no global rule on the model. Group-scoped rules do not restrict non-members, so the portal ACL row is unscoped.

Any authenticated portal user can therefore, over plain RPC:

  • search_read every grievance in the system — other people's complaints, contact details, descriptions, assignments, escalation state (grievances routinely contain sensitive personal information, and GRM sensitivity levels exist precisely because of that);
  • write on any ticket — rewrite descriptions, flip stage_id (which triggers escalation evaluation), reassign, or close other registrants' grievances;
  • create tickets directly, bypassing the portal controller (spp_grm/controllers/grm_portal.py runs sudo and is not the enforcement point).

The portal UI's apparent scoping comes only from the controller/views; the ORM surface is unscoped.

Why this is the root of the PR #399 finding-11 cluster

The retained portal read row on spp.grm.routing.rule / spp.grm.escalation.rule (kept in #266, questioned in the PR #399 review) is load-bearing only because portal users can create/stage-write tickets over direct RPC, which evaluates rules as the acting user. Scoping portal ticket access changes that premise, so the pieces belong to one coherent fix:

  1. Portal record rule on spp.grm.ticket — own tickets only (e.g. [('partner_id', '=', user.partner_id.id)]; decide whether portal keeps write at all, or read-only + controller-mediated updates).
  2. Move rule evaluation to sudo() in spp_grm_cel (apply_routing / apply_escalations) — rules are staff-authored trusted config after security(grm): restrict GRM automation rules to GRM staff (drop portal write/create) #266; the ticket recordset keeps the caller's env so write-rights semantics are unchanged.
  3. Drop the portal AND base.group_user read rows on both rule models — closes the enumeration surface (any portal user can currently map every routing/escalation condition, threshold, and target assignee/team; Odoo 19's Many2one.convert_to_read returns display names via sudo, so officer names leak despite the core res.users portal rule).
  4. Replace test_portal_user_can_read_rules with a read-denial test and update the module docstring (already reworded in security: batch 1 — DCI/Key admin privilege escalations, OAuth signing keys, GRM rule ACL (#327, #329, #265, #266) #399 to call the grant a "current implementation dependency").

Items 2-4 are the finding-11 follow-up from the PR #399 review; item 1 is the new gap that makes them safe to do.

Notes

  • The missing-record-rule pattern is the exact "Related models need record rules too" pitfall; other spp_grm models with portal rows (stage, tag, channel, category, subcategory, team, sla_rule — all read-only) are global config and arguably fine, but worth a pass in the same PR.
  • Any fix ships with a version bump + migration considerations for deployments that relied on portal RPC access (none known — the portal UI goes through the controller).
  • Tests must cover: portal user cannot read/write another partner's ticket; portal user still sees their own; controller-submitted tickets still route; staff access unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions