SEC2-004: Giving a record to a group fails with an unhandled exception when what it points at points at something the group cannot see #946
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#946
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-10-16 in a read-only security review of
postuloat56fc9d531, checked by a second, independent pass.Severity: Informational · Category: Business Logic · Reference: CWE-754 (Improper Check for Unusual or Exceptional Conditions); OWASP A04:2021 (Insecure Design)
Location:
src/postulo/jobs/ownership.py:77-95(move)src/postulo/jobs/sharing.py:127-150(share)src/postulo/jobs/share_views.py:205-209(MoveView.post)Issue
references()walks transitively, so for a listing whose company has a parent the plan isalso = [company, parent].movethen shares each one on its own, withoutalso_share:share(company, group, READ, actor)re-plans for the company, finds the parent not yet seen by the group, and sincealso_sharedefaults toFalse:MoveView.postcatches onlyownership.MoveRefused, so theShareRefusedescapes as a 500. The transaction rolls back, so nothing is shared or moved; the giver sees the error page after having confirmed the move. The same page already computed and showed the fullplan.also, so the person agreed to all of it.Impact
No security consequence: nothing is moved or shared. A member who tries to give a listing (or a contact at a subsidiary) to a group hits a server error and cannot complete the action through the interface. Recorded because an unhandled error on a confirmed state change tends to be retried in ways that bypass the confirmation.
Fix
In
ownership.move, pass the flag through atownership.py:95:sharing.share(target, principal, Share.Scope.READ, actor, also_share=also_share). Sharing a target's references is idempotent through_grant, and they are already inplan.also. (Sharingplan.alsoin reverse order would also work for a chain but not for a diamond.) Catchsharing.ShareRefusedbesideMoveRefusedinMoveView.postregardless. Done when a test moves a listing whose company has a parent into a group neither is visible to, withconfirmed=1, and gets a redirect with the record owned by the group and both companies shared read-only.