fix: sanitize error output in server validation logs (#9197)

This commit is contained in:
Andras Bacsai
2026-03-28 12:13:50 +01:00
committed by GitHub
7 changed files with 102 additions and 5 deletions
+2 -1
View File
@@ -30,7 +30,8 @@ class ValidateServer
]);
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$sanitizedError.'</div>';
$server->update([
'validation_logs' => $this->error,
]);
+3 -2
View File
@@ -45,7 +45,8 @@ class ValidateAndInstallServerJob implements ShouldBeEncrypted, ShouldQueue
// Validate connection
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if (! $uptime) {
$errorMessage = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error;
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
$errorMessage = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$sanitizedError;
$this->server->update([
'validation_logs' => $errorMessage,
'is_validating' => false,
@@ -197,7 +198,7 @@ class ValidateAndInstallServerJob implements ShouldBeEncrypted, ShouldQueue
]);
$this->server->update([
'validation_logs' => 'An error occurred during validation: '.$e->getMessage(),
'validation_logs' => 'An error occurred during validation: '.htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'),
'is_validating' => false,
]);
}
+2 -1
View File
@@ -63,7 +63,8 @@ class Show extends Component
$this->dispatch('success', 'Server is reachable.');
$this->dispatch('refreshServerShow');
} else {
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$sanitizedError);
return;
}
+2 -1
View File
@@ -89,7 +89,8 @@ class ValidateAndInstall extends Component
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$sanitizedError.'</div>';
$this->server->update([
'validation_logs' => $this->error,
]);
+7
View File
@@ -269,6 +269,13 @@ class Server extends BaseModel
use HasSafeStringAttribute;
public function setValidationLogsAttribute($value): void
{
$this->attributes['validation_logs'] = $value !== null
? \Stevebauman\Purify\Facades\Purify::config('validation_logs')->clean($value)
: null;
}
public function type()
{
return 'server';
+11
View File
@@ -49,6 +49,17 @@ return [
'AutoFormat.RemoveEmpty' => false,
],
'validation_logs' => [
'Core.Encoding' => 'utf-8',
'HTML.Doctype' => 'HTML 4.01 Transitional',
'HTML.Allowed' => 'a[href|title|target|class],br,div[class],pre[class],span[class],p[class]',
'HTML.ForbiddenElements' => '',
'CSS.AllowedProperties' => '',
'AutoFormat.AutoParagraph' => false,
'AutoFormat.RemoveEmpty' => false,
'Attr.AllowedFrameTargets' => ['_blank'],
],
],
/*
+75
View File
@@ -0,0 +1,75 @@
<?php
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$user = User::factory()->create();
$this->team = Team::factory()->create();
$user->teams()->attach($this->team);
$this->actingAs($user);
session(['currentTeam' => $this->team]);
$this->server = Server::factory()->create([
'team_id' => $this->team->id,
]);
});
it('strips dangerous HTML from validation_logs via mutator', function () {
$xssPayload = '<img src=x onerror=alert(document.domain)>';
$this->server->update(['validation_logs' => $xssPayload]);
$this->server->refresh();
expect($this->server->validation_logs)->not->toContain('<img')
->and($this->server->validation_logs)->not->toContain('onerror');
});
it('strips script tags from validation_logs', function () {
$xssPayload = '<script>alert("xss")</script>';
$this->server->update(['validation_logs' => $xssPayload]);
$this->server->refresh();
expect($this->server->validation_logs)->not->toContain('<script');
});
it('preserves allowed HTML in validation_logs', function () {
$allowedHtml = 'Server is not reachable.<br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs">documentation</a> for further help.<br><br><div class="text-error">Error: Connection refused</div>';
$this->server->update(['validation_logs' => $allowedHtml]);
$this->server->refresh();
expect($this->server->validation_logs)->toContain('<a')
->and($this->server->validation_logs)->toContain('<br')
->and($this->server->validation_logs)->toContain('<div')
->and($this->server->validation_logs)->toContain('Connection refused');
});
it('allows null validation_logs', function () {
$this->server->update(['validation_logs' => null]);
$this->server->refresh();
expect($this->server->validation_logs)->toBeNull();
});
it('sanitizes XSS embedded within valid error HTML', function () {
$maliciousError = 'Server is not reachable.<br><div class="text-error">Error: <img src=x onerror=alert(document.cookie)></div>';
$this->server->update(['validation_logs' => $maliciousError]);
$this->server->refresh();
expect($this->server->validation_logs)->toContain('<div')
->and($this->server->validation_logs)->toContain('Error:')
->and($this->server->validation_logs)->not->toContain('onerror')
->and($this->server->validation_logs)->not->toContain('<img');
});
it('sanitizes event handler attributes in validation_logs', function () {
$payload = '<div onmouseover="alert(1)" class="text-error">Error</div>';
$this->server->update(['validation_logs' => $payload]);
$this->server->refresh();
expect($this->server->validation_logs)->toContain('<div')
->and($this->server->validation_logs)->not->toContain('onmouseover');
});