mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
fix(team): build invite links from instance URL, not request host
When no instance domain is set, invitation links were built from the request host, which a client can spoof through a forwarded Host header. Links now always use base_url(). The fallback test now forces a spoofed root URL and checks that the link ignores it.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
type: security
|
||||
---
|
||||
Built team invitation links from the instance URL instead of the request host, when no instance domain is set
|
||||
@@ -66,14 +66,12 @@ class InviteLink extends Component
|
||||
$this->generateInviteLink(sendEmail: false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the link from the instance URL, not from the request host, which a client can set.
|
||||
*/
|
||||
private function invitationUrl(string $routeName, array $parameters): string
|
||||
{
|
||||
$fqdn = instanceSettings()->fqdn;
|
||||
if (filled($fqdn)) {
|
||||
return rtrim($fqdn, '/').route($routeName, $parameters, false);
|
||||
}
|
||||
|
||||
return route($routeName, $parameters);
|
||||
return rtrim(base_url(), '/').route($routeName, $parameters, false);
|
||||
}
|
||||
|
||||
private function generateInviteLink(bool $sendEmail = false)
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
@@ -185,15 +186,19 @@ describe('privilege escalation prevention', function () {
|
||||
expect($invitation->link)->toStartWith('https://coolify.example.com/auth/link?token=');
|
||||
});
|
||||
|
||||
test('new user invitation magic link falls back to route url when instance fqdn is not configured', function () {
|
||||
test('new user invitation magic link ignores a spoofed request host when instance fqdn is not configured', function () {
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::query()->updateOrCreate(
|
||||
['id' => 0],
|
||||
['fqdn' => null]
|
||||
['fqdn' => null, 'public_ipv4' => null, 'public_ipv6' => null]
|
||||
));
|
||||
config(['app.url' => 'http://coolify.internal:8000']);
|
||||
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
// A forwarded Host header from the client sets the request root URL.
|
||||
URL::forceRootUrl('http://evil.example');
|
||||
|
||||
Livewire::test(InviteLink::class)
|
||||
->set('email', 'fallback-invitee@example.com')
|
||||
->set('role', 'member')
|
||||
@@ -202,8 +207,8 @@ describe('privilege escalation prevention', function () {
|
||||
|
||||
$invitation = TeamInvitation::whereEmail('fallback-invitee@example.com')->firstOrFail();
|
||||
|
||||
$expectedPrefix = route('auth.link', ['token' => '']);
|
||||
expect($invitation->link)->toStartWith($expectedPrefix);
|
||||
expect($invitation->link)->toStartWith('http://coolify.internal:8000/auth/link?token=')
|
||||
->not->toContain('evil.example');
|
||||
});
|
||||
|
||||
test('member cannot bypass policy by calling viaEmail', function () {
|
||||
|
||||
Reference in New Issue
Block a user