mirror of
https://github.com/coollabsio/coolify.git
synced 2025-12-28 05:34:50 +00:00
This commit addresses a critical security vulnerability where low-privileged
users (members) could invite high-privileged users (admins/owners) to teams,
allowing them to escalate their own privileges through password reset.
Root Causes Fixed:
1. TeamPolicy authorization checks were commented out, allowing all team
members to manage invitations instead of just admins/owners
2. Missing role elevation checks in InviteLink component allowed members
to invite users with higher privileges
Security Fixes:
1. app/Policies/TeamPolicy.php
- Uncommented and enforced authorization checks for:
* update() - Only admins/owners can update team settings
* delete() - Only admins/owners can delete teams
* manageMembers() - Only admins/owners can manage team members
* viewAdmin() - Only admins/owners can view admin panel
* manageInvitations() - Only admins/owners can manage invitations
2. app/Livewire/Team/InviteLink.php
- Added explicit role elevation checks to prevent:
* Members from inviting admins or owners
* Admins from inviting owners (defense-in-depth)
- Validates that inviter has sufficient privileges for target role
Test Coverage:
1. tests/Feature/TeamPolicyTest.php
- 24 comprehensive tests covering all policy methods
- Tests for owner, admin, member, and non-member access
- Specific tests for the privilege escalation vulnerability
2. tests/Feature/TeamInvitationPrivilegeEscalationTest.php
- 11 tests covering all role elevation scenarios
- Tests member → admin/owner escalation (blocked)
- Tests admin → owner escalation (blocked)
- Tests valid invitation paths for each role
Impact:
- Prevents privilege escalation attacks
- Protects all Coolify instances from unauthorized access
- Enforces proper role hierarchy in team management
References:
- Identified by Aikido AI whitebox pentest service
- CVE: Pending assignment
- Severity: Critical
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
177 lines
5.6 KiB
PHP
177 lines
5.6 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Team\InviteLink;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
// Create a team with owner, admin, and member
|
|
$this->team = Team::factory()->create();
|
|
|
|
$this->owner = User::factory()->create();
|
|
$this->admin = User::factory()->create();
|
|
$this->member = User::factory()->create();
|
|
|
|
$this->team->members()->attach($this->owner->id, ['role' => 'owner']);
|
|
$this->team->members()->attach($this->admin->id, ['role' => 'admin']);
|
|
$this->team->members()->attach($this->member->id, ['role' => 'member']);
|
|
});
|
|
|
|
describe('privilege escalation prevention', function () {
|
|
test('member cannot invite admin (SECURITY FIX)', function () {
|
|
// Login as member
|
|
$this->actingAs($this->member);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Attempt to invite someone as admin
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newadmin@example.com')
|
|
->set('role', 'admin')
|
|
->call('viaLink')
|
|
->assertDispatched('error');
|
|
});
|
|
|
|
test('member cannot invite owner (SECURITY FIX)', function () {
|
|
// Login as member
|
|
$this->actingAs($this->member);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Attempt to invite someone as owner
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newowner@example.com')
|
|
->set('role', 'owner')
|
|
->call('viaLink')
|
|
->assertDispatched('error');
|
|
});
|
|
|
|
test('admin cannot invite owner', function () {
|
|
// Login as admin
|
|
$this->actingAs($this->admin);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Attempt to invite someone as owner
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newowner@example.com')
|
|
->set('role', 'owner')
|
|
->call('viaLink')
|
|
->assertDispatched('error');
|
|
});
|
|
|
|
test('admin can invite member', function () {
|
|
// Login as admin
|
|
$this->actingAs($this->admin);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Invite someone as member
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newmember@example.com')
|
|
->set('role', 'member')
|
|
->call('viaLink')
|
|
->assertDispatched('success');
|
|
|
|
// Verify invitation was created
|
|
$this->assertDatabaseHas('team_invitations', [
|
|
'email' => 'newmember@example.com',
|
|
'role' => 'member',
|
|
'team_id' => $this->team->id,
|
|
]);
|
|
});
|
|
|
|
test('admin can invite admin', function () {
|
|
// Login as admin
|
|
$this->actingAs($this->admin);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Invite someone as admin
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newadmin@example.com')
|
|
->set('role', 'admin')
|
|
->call('viaLink')
|
|
->assertDispatched('success');
|
|
|
|
// Verify invitation was created
|
|
$this->assertDatabaseHas('team_invitations', [
|
|
'email' => 'newadmin@example.com',
|
|
'role' => 'admin',
|
|
'team_id' => $this->team->id,
|
|
]);
|
|
});
|
|
|
|
test('owner can invite member', function () {
|
|
// Login as owner
|
|
$this->actingAs($this->owner);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Invite someone as member
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newmember@example.com')
|
|
->set('role', 'member')
|
|
->call('viaLink')
|
|
->assertDispatched('success');
|
|
|
|
// Verify invitation was created
|
|
$this->assertDatabaseHas('team_invitations', [
|
|
'email' => 'newmember@example.com',
|
|
'role' => 'member',
|
|
'team_id' => $this->team->id,
|
|
]);
|
|
});
|
|
|
|
test('owner can invite admin', function () {
|
|
// Login as owner
|
|
$this->actingAs($this->owner);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Invite someone as admin
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newadmin@example.com')
|
|
->set('role', 'admin')
|
|
->call('viaLink')
|
|
->assertDispatched('success');
|
|
|
|
// Verify invitation was created
|
|
$this->assertDatabaseHas('team_invitations', [
|
|
'email' => 'newadmin@example.com',
|
|
'role' => 'admin',
|
|
'team_id' => $this->team->id,
|
|
]);
|
|
});
|
|
|
|
test('owner can invite owner', function () {
|
|
// Login as owner
|
|
$this->actingAs($this->owner);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Invite someone as owner
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newowner@example.com')
|
|
->set('role', 'owner')
|
|
->call('viaLink')
|
|
->assertDispatched('success');
|
|
|
|
// Verify invitation was created
|
|
$this->assertDatabaseHas('team_invitations', [
|
|
'email' => 'newowner@example.com',
|
|
'role' => 'owner',
|
|
'team_id' => $this->team->id,
|
|
]);
|
|
});
|
|
|
|
test('member cannot bypass policy by calling viaEmail', function () {
|
|
// Login as member
|
|
$this->actingAs($this->member);
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
// Attempt to invite someone as admin via email
|
|
Livewire::test(InviteLink::class)
|
|
->set('email', 'newadmin@example.com')
|
|
->set('role', 'admin')
|
|
->call('viaEmail')
|
|
->assertDispatched('error');
|
|
});
|
|
});
|