Skip to content

Security: Livewire action methods bypass Spatie permission checks #79

Description

@lighthousekeeper1212

Summary

Multiple Livewire components bypass route-level Spatie permission enforcement, allowing read-only CRM users to delete records, change deal statuses, and even delete system users across teams.

Findings

1. Livewire action methods bypass Spatie permission checks (HIGH)

15+ Livewire Index/Show components have action methods (delete(), won(), lost(), accept(), reject()) with no authorization. Routes enforce can: middleware, but Livewire POST to /livewire/message bypasses it entirely.

Affected: DealIndex, LeadIndex, PersonIndex, OrganizationIndex, QuoteIndex, OrderIndex, InvoiceIndex, PurchaseOrderIndex, DeliveryIndex, ProductShow, PersonShow, LeadShow, OrganizationShow, UserIndex, TeamIndex

2. Cross-team user deletion without authorization (HIGH)

Livewire/Users/UserIndex.phpdelete($id) uses User::find($id) which does NOT have BelongsToTeams scope. Any authenticated user can delete ANY system user including cross-team users or admins.

3. Cross-team role assignment (HIGH)

UserController.phpRole::find($request->role) fetches roles from any team. Admin in Team A can assign Team B's 'Owner' role to users.

4. Activities routes missing permission middleware (MEDIUM)

routes.php lines 512-533 — Activity routes have only auth middleware, no can: checks, unlike all other resource routes.

5. Sub-resource routes missing permission middleware (MEDIUM)

Deal/Quote/Order product routes lack can: middleware. View-only users can create product lines.

Root Cause

Route-level can: middleware correctly enforces Spatie permissions for page loads, but Livewire action methods are called via /livewire/message POST which bypasses route middleware. None of the Livewire action methods call $this->authorize() or check permissions server-side.

Recommended Fix

Add $this->authorize() to all Livewire action methods:

public function delete($id)
{
    if ($deal = Deal::find($id)) {
        $this->authorize('delete', $deal);  // ADD THIS
        $deal->delete();
    }
}

For UserIndex::delete(), also scope to current team. For role assignment, filter: Role::where('team_id', $user->currentTeam->id)->find($request->role).

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions