From 6197558a38f5afbb74f138be59b52e705c99bf1e Mon Sep 17 00:00:00 2001
From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
Date: Sat, 28 Mar 2026 21:08:48 +0530
Subject: [PATCH 01/12] fix(validation): add input validation for resource
limit fields
---
.../Project/Shared/ResourceLimits.php | 44 ++++++++++++++-----
1 file changed, 32 insertions(+), 12 deletions(-)
diff --git a/app/Livewire/Project/Shared/ResourceLimits.php b/app/Livewire/Project/Shared/ResourceLimits.php
index 0b3840289..8a14dc10c 100644
--- a/app/Livewire/Project/Shared/ResourceLimits.php
+++ b/app/Livewire/Project/Shared/ResourceLimits.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Shared;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Validation\ValidationException;
use Livewire\Component;
class ResourceLimits extends Component
@@ -16,24 +17,24 @@ class ResourceLimits extends Component
public ?string $limitsCpuset = null;
- public ?int $limitsCpuShares = null;
+ public mixed $limitsCpuShares = null;
public string $limitsMemory;
public string $limitsMemorySwap;
- public int $limitsMemorySwappiness;
+ public mixed $limitsMemorySwappiness = 0;
public string $limitsMemoryReservation;
protected $rules = [
- 'limitsMemory' => 'required|string',
- 'limitsMemorySwap' => 'required|string',
+ 'limitsMemory' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
+ 'limitsMemorySwap' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
'limitsMemorySwappiness' => 'required|integer|min:0|max:100',
- 'limitsMemoryReservation' => 'required|string',
- 'limitsCpus' => 'nullable',
- 'limitsCpuset' => 'nullable',
- 'limitsCpuShares' => 'nullable',
+ 'limitsMemoryReservation' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
+ 'limitsCpus' => ['nullable', 'regex:/^\d*\.?\d+$/'],
+ 'limitsCpuset' => ['nullable', 'regex:/^\d+([,-]\d+)*$/'],
+ 'limitsCpuShares' => 'nullable|integer|min:0',
];
protected $validationAttributes = [
@@ -46,6 +47,19 @@ class ResourceLimits extends Component
'limitsCpuShares' => 'cpu shares',
];
+ protected $messages = [
+ 'limitsMemory.regex' => 'Maximum Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
+ 'limitsMemorySwap.regex' => 'Maximum Swap Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
+ 'limitsMemoryReservation.regex' => 'Soft Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
+ 'limitsCpus.regex' => 'Number of CPUs must be a number (integer or decimal). Example: 0.5, 2.',
+ 'limitsCpuset.regex' => 'CPU sets must be a comma-separated list of CPU numbers or ranges. Example: 0-2 or 0,1,3.',
+ 'limitsMemorySwappiness.integer' => 'Swappiness must be a whole number between 0 and 100.',
+ 'limitsMemorySwappiness.min' => 'Swappiness must be between 0 and 100.',
+ 'limitsMemorySwappiness.max' => 'Swappiness must be between 0 and 100.',
+ 'limitsCpuShares.integer' => 'CPU Weight must be a whole number.',
+ 'limitsCpuShares.min' => 'CPU Weight must be a positive number.',
+ ];
+
/**
* Sync data between component properties and model
*
@@ -57,10 +71,10 @@ private function syncData(bool $toModel = false): void
// Sync TO model (before save)
$this->resource->limits_cpus = $this->limitsCpus;
$this->resource->limits_cpuset = $this->limitsCpuset;
- $this->resource->limits_cpu_shares = $this->limitsCpuShares;
+ $this->resource->limits_cpu_shares = (int) $this->limitsCpuShares;
$this->resource->limits_memory = $this->limitsMemory;
$this->resource->limits_memory_swap = $this->limitsMemorySwap;
- $this->resource->limits_memory_swappiness = $this->limitsMemorySwappiness;
+ $this->resource->limits_memory_swappiness = (int) $this->limitsMemorySwappiness;
$this->resource->limits_memory_reservation = $this->limitsMemoryReservation;
} else {
// Sync FROM model (on load/refresh)
@@ -91,7 +105,7 @@ public function submit()
if (! $this->limitsMemorySwap) {
$this->limitsMemorySwap = '0';
}
- if (is_null($this->limitsMemorySwappiness)) {
+ if ($this->limitsMemorySwappiness === '' || is_null($this->limitsMemorySwappiness)) {
$this->limitsMemorySwappiness = 60;
}
if (! $this->limitsMemoryReservation) {
@@ -103,7 +117,7 @@ public function submit()
if ($this->limitsCpuset === '') {
$this->limitsCpuset = null;
}
- if (is_null($this->limitsCpuShares)) {
+ if ($this->limitsCpuShares === '' || is_null($this->limitsCpuShares)) {
$this->limitsCpuShares = 1024;
}
@@ -112,6 +126,12 @@ public function submit()
$this->syncData(true);
$this->resource->save();
$this->dispatch('success', 'Resource limits updated.');
+ } catch (ValidationException $e) {
+ foreach ($e->validator->errors()->all() as $message) {
+ $this->dispatch('error', $message);
+ }
+
+ return;
} catch (\Throwable $e) {
return handleError($e, $this);
}
From 407b6df7440d90f324a578ee0b0ebd10cae1da6a Mon Sep 17 00:00:00 2001
From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
Date: Sat, 28 Mar 2026 22:30:17 +0530
Subject: [PATCH 02/12] fix(validation): add IP validation for custom DNS
servers input
---
app/Livewire/Settings/Advanced.php | 4 ++--
app/Rules/ValidDnsServers.php | 35 ++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 app/Rules/ValidDnsServers.php
diff --git a/app/Livewire/Settings/Advanced.php b/app/Livewire/Settings/Advanced.php
index ad478273f..f4b57ae20 100644
--- a/app/Livewire/Settings/Advanced.php
+++ b/app/Livewire/Settings/Advanced.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Settings;
use App\Models\InstanceSettings;
+use App\Rules\ValidDnsServers;
use App\Rules\ValidIpOrCidr;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -20,7 +21,6 @@ class Advanced extends Component
#[Validate('boolean')]
public bool $is_dns_validation_enabled;
- #[Validate('nullable|string')]
public ?string $custom_dns_servers = null;
#[Validate('boolean')]
@@ -43,7 +43,7 @@ public function rules()
'is_registration_enabled' => 'boolean',
'do_not_track' => 'boolean',
'is_dns_validation_enabled' => 'boolean',
- 'custom_dns_servers' => 'nullable|string',
+ 'custom_dns_servers' => ['nullable', 'string', new ValidDnsServers],
'is_api_enabled' => 'boolean',
'allowed_ips' => ['nullable', 'string', new ValidIpOrCidr],
'is_sponsorship_popup_enabled' => 'boolean',
diff --git a/app/Rules/ValidDnsServers.php b/app/Rules/ValidDnsServers.php
new file mode 100644
index 000000000..e3bbd048f
--- /dev/null
+++ b/app/Rules/ValidDnsServers.php
@@ -0,0 +1,35 @@
+
Date: Sat, 28 Mar 2026 23:23:25 +0530
Subject: [PATCH 03/12] fix(validation): add input validation for port exposes
and port mappings fields
---
app/Livewire/Project/Application/General.php | 12 ++++++++--
.../Project/Database/Clickhouse/General.php | 6 ++++-
.../Project/Database/Dragonfly/General.php | 6 ++++-
.../Project/Database/Keydb/General.php | 6 ++++-
.../Project/Database/Mariadb/General.php | 6 ++++-
.../Project/Database/Mongodb/General.php | 6 ++++-
.../Project/Database/Mysql/General.php | 6 ++++-
.../Project/Database/Postgresql/General.php | 6 ++++-
.../Project/Database/Redis/General.php | 6 ++++-
app/Support/ValidationPatterns.php | 24 +++++++++++++++++++
10 files changed, 74 insertions(+), 10 deletions(-)
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 5c186af70..3e78c1732 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -153,8 +153,8 @@ protected function rules(): array
'staticImage' => 'required',
'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
'publishDirectory' => ValidationPatterns::directoryPathRules(),
- 'portsExposes' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsExposes' => ['required', 'string', 'regex:/^(\d+)(,\d+)*$/'],
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'customNetworkAliases' => 'nullable',
'dockerfile' => 'nullable',
'dockerRegistryImageName' => 'nullable',
@@ -209,6 +209,8 @@ protected function messages(): array
'staticImage.required' => 'The Static Image field is required.',
'baseDirectory.required' => 'The Base Directory field is required.',
'portsExposes.required' => 'The Exposed Ports field is required.',
+ 'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
+ ...ValidationPatterns::portMappingMessages(),
'isStatic.required' => 'The Static setting is required.',
'isStatic.boolean' => 'The Static setting must be true or false.',
'isSpa.required' => 'The SPA setting is required.',
@@ -752,6 +754,12 @@ public function submit($showToaster = true)
$this->authorize('update', $this->application);
$this->resetErrorBag();
+
+ $this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString();
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
+
$this->validate();
$oldPortsExposes = $this->application->ports_exposes;
diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php
index 9de75c1c5..0913ca797 100644
--- a/app/Livewire/Project/Database/Clickhouse/General.php
+++ b/app/Livewire/Project/Database/Clickhouse/General.php
@@ -79,7 +79,7 @@ protected function rules(): array
'clickhouseAdminUser' => 'required|string',
'clickhouseAdminPassword' => 'required|string',
'image' => 'required|string',
- 'portsMappings' => 'nullable|string',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -94,6 +94,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'clickhouseAdminUser.required' => 'The Admin User field is required.',
'clickhouseAdminUser.string' => 'The Admin User must be a string.',
@@ -207,6 +208,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php
index d35e57a9d..23503bd98 100644
--- a/app/Livewire/Project/Database/Dragonfly/General.php
+++ b/app/Livewire/Project/Database/Dragonfly/General.php
@@ -90,7 +90,7 @@ protected function rules(): array
'description' => ValidationPatterns::descriptionRules(),
'dragonflyPassword' => 'required|string',
'image' => 'required|string',
- 'portsMappings' => 'nullable|string',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -106,6 +106,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'dragonflyPassword.required' => 'The Dragonfly Password field is required.',
'dragonflyPassword.string' => 'The Dragonfly Password must be a string.',
@@ -217,6 +218,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php
index adb4ccb5f..ff9dc19ad 100644
--- a/app/Livewire/Project/Database/Keydb/General.php
+++ b/app/Livewire/Project/Database/Keydb/General.php
@@ -93,7 +93,7 @@ protected function rules(): array
'keydbConf' => 'nullable|string',
'keydbPassword' => 'required|string',
'image' => 'required|string',
- 'portsMappings' => 'nullable|string',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -111,6 +111,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'keydbPassword.required' => 'The KeyDB Password field is required.',
'keydbPassword.string' => 'The KeyDB Password must be a string.',
@@ -224,6 +225,9 @@ public function submit()
try {
$this->authorize('manageEnvironment', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php
index 14240c82d..a9ac47b97 100644
--- a/app/Livewire/Project/Database/Mariadb/General.php
+++ b/app/Livewire/Project/Database/Mariadb/General.php
@@ -78,7 +78,7 @@ protected function rules(): array
'mariadbDatabase' => 'required',
'mariadbConf' => 'nullable',
'image' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -92,6 +92,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mariadbRootPassword.required' => 'The Root Password field is required.',
@@ -213,6 +214,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php
index 11419ec71..2b6538edf 100644
--- a/app/Livewire/Project/Database/Mongodb/General.php
+++ b/app/Livewire/Project/Database/Mongodb/General.php
@@ -77,7 +77,7 @@ protected function rules(): array
'mongoInitdbRootPassword' => 'required',
'mongoInitdbDatabase' => 'required',
'image' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -92,6 +92,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
@@ -213,6 +214,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php
index 4f0f5eb19..f3c554522 100644
--- a/app/Livewire/Project/Database/Mysql/General.php
+++ b/app/Livewire/Project/Database/Mysql/General.php
@@ -80,7 +80,7 @@ protected function rules(): array
'mysqlDatabase' => 'required',
'mysqlConf' => 'nullable',
'image' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -95,6 +95,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mysqlRootPassword.required' => 'The Root Password field is required.',
@@ -220,6 +221,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php
index 4e044672b..7a4ff057e 100644
--- a/app/Livewire/Project/Database/Postgresql/General.php
+++ b/app/Livewire/Project/Database/Postgresql/General.php
@@ -92,7 +92,7 @@ protected function rules(): array
'postgresConf' => 'nullable',
'initScripts' => 'nullable',
'image' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -107,6 +107,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'postgresUser.required' => 'The Postgres User field is required.',
@@ -456,6 +457,9 @@ public function submit()
try {
$this->authorize('update', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php
index ebe2f3ba0..a1a22ab8b 100644
--- a/app/Livewire/Project/Database/Redis/General.php
+++ b/app/Livewire/Project/Database/Redis/General.php
@@ -73,7 +73,7 @@ protected function rules(): array
'description' => ValidationPatterns::descriptionRules(),
'redisConf' => 'nullable',
'image' => 'required',
- 'portsMappings' => 'nullable',
+ 'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -89,6 +89,7 @@ protected function messages(): array
{
return array_merge(
ValidationPatterns::combinedMessages(),
+ ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'image.required' => 'The Docker Image field is required.',
@@ -201,6 +202,9 @@ public function submit()
try {
$this->authorize('manageEnvironment', $this->database);
+ if ($this->portsMappings) {
+ $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
+ }
$this->syncData(true);
if (version_compare($this->redisVersion, '6.0', '>=')) {
diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php
index 7084b4cc2..5d53076ea 100644
--- a/app/Support/ValidationPatterns.php
+++ b/app/Support/ValidationPatterns.php
@@ -194,6 +194,12 @@ public static function volumeNameMessages(string $field = 'name'): array
];
}
+ /**
+ * Pattern for port mappings (e.g. 3000:3000, 8080:80, 8000-8010:8000-8010)
+ * Each entry requires host:container format, where each side can be a number or a range (number-number)
+ */
+ public const PORT_MAPPINGS_PATTERN = '/^(\d+(-\d+)?:\d+(-\d+)?)(,\d+(-\d+)?:\d+(-\d+)?)*$/';
+
/**
* Get validation rules for container name fields
*/
@@ -202,6 +208,24 @@ public static function containerNameRules(int $maxLength = 255): array
return ['string', 'max:'.$maxLength, 'regex:'.self::CONTAINER_NAME_PATTERN];
}
+ /**
+ * Get validation rules for port mapping fields
+ */
+ public static function portMappingRules(): array
+ {
+ return ['nullable', 'string', 'regex:'.self::PORT_MAPPINGS_PATTERN];
+ }
+
+ /**
+ * Get validation messages for port mapping fields
+ */
+ public static function portMappingMessages(string $field = 'portsMappings'): array
+ {
+ return [
+ "{$field}.regex" => 'Port mappings must be a comma-separated list of port pairs or ranges (e.g. 3000:3000,8080:80,8000-8010:8000-8010).',
+ ];
+ }
+
/**
* Check if a string is a valid Docker container name.
*/
From 73258c317e3d79aefb90924d5319ddc54209eebc Mon Sep 17 00:00:00 2001
From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
Date: Sun, 29 Mar 2026 00:34:32 +0530
Subject: [PATCH 04/12] fix(validation): add URL validation for proxy redirect
input
---
app/Livewire/Server/Proxy.php | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php
index d5f30fca0..c2d8205ef 100644
--- a/app/Livewire/Server/Proxy.php
+++ b/app/Livewire/Server/Proxy.php
@@ -6,6 +6,7 @@
use App\Actions\Proxy\SaveProxyConfiguration;
use App\Enums\ProxyTypes;
use App\Models\Server;
+use App\Rules\SafeExternalUrl;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -41,9 +42,13 @@ public function getListeners()
];
}
- protected $rules = [
- 'generateExactLabels' => 'required|boolean',
- ];
+ protected function rules()
+ {
+ return [
+ 'generateExactLabels' => 'required|boolean',
+ 'redirectUrl' => ['nullable', new SafeExternalUrl],
+ ];
+ }
public function mount()
{
@@ -147,6 +152,7 @@ public function submit()
{
try {
$this->authorize('update', $this->server);
+ $this->validate();
SaveProxyConfiguration::run($this->server, $this->proxySettings);
$this->server->proxy->redirect_url = $this->redirectUrl;
$this->server->save();
From c52a199120d2d06e2a473305734643f2bd66ead1 Mon Sep 17 00:00:00 2001
From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com>
Date: Sun, 29 Mar 2026 01:14:08 +0530
Subject: [PATCH 05/12] fix(validation): add input validation for server
advanced settings page
---
app/Http/Controllers/Api/ServersController.php | 18 +++++++++++++++++-
app/Livewire/Server/Advanced.php | 16 ++++++++--------
.../views/livewire/server/advanced.blade.php | 4 ++++
3 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
index 2ef95ce8b..930879d80 100644
--- a/app/Http/Controllers/Api/ServersController.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -598,6 +598,11 @@ public function create_server(Request $request)
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
+ 'concurrent_builds' => ['type' => 'integer', 'description' => 'Number of concurrent builds.'],
+ 'dynamic_timeout' => ['type' => 'integer', 'description' => 'Deployment timeout in seconds.'],
+ 'deployment_queue_limit' => ['type' => 'integer', 'description' => 'Maximum number of queued deployments.'],
+ 'server_disk_usage_notification_threshold' => ['type' => 'integer', 'description' => 'Server disk usage notification threshold (%).'],
+ 'server_disk_usage_check_frequency' => ['type' => 'string', 'description' => 'Cron expression for disk usage check frequency.'],
],
),
),
@@ -634,7 +639,7 @@ public function create_server(Request $request)
)]
public function update_server(Request $request)
{
- $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
+ $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type', 'concurrent_builds', 'dynamic_timeout', 'deployment_queue_limit', 'server_disk_usage_notification_threshold', 'server_disk_usage_check_frequency'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
@@ -655,6 +660,11 @@ public function update_server(Request $request)
'is_build_server' => 'boolean|nullable',
'instant_validate' => 'boolean|nullable',
'proxy_type' => 'string|nullable',
+ 'concurrent_builds' => 'integer|nullable|min:1',
+ 'dynamic_timeout' => 'integer|nullable|min:1',
+ 'deployment_queue_limit' => 'integer|nullable|min:1',
+ 'server_disk_usage_notification_threshold' => 'integer|nullable|min:1|max:100',
+ 'server_disk_usage_check_frequency' => 'string|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
@@ -691,6 +701,12 @@ public function update_server(Request $request)
'is_build_server' => $request->is_build_server,
]);
}
+
+ $advancedSettings = $request->only(['concurrent_builds', 'dynamic_timeout', 'deployment_queue_limit', 'server_disk_usage_notification_threshold', 'server_disk_usage_check_frequency']);
+ if (! empty($advancedSettings)) {
+ $server->settings()->update(array_filter($advancedSettings, fn ($value) => ! is_null($value)));
+ }
+
if ($request->instant_validate) {
ValidateServer::dispatch($server);
}
diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php
index dba1b4903..0e1a9a325 100644
--- a/app/Livewire/Server/Advanced.php
+++ b/app/Livewire/Server/Advanced.php
@@ -15,17 +15,17 @@ class Advanced extends Component
#[Validate(['string'])]
public string $serverDiskUsageCheckFrequency = '0 23 * * *';
- #[Validate(['integer', 'min:1', 'max:99'])]
- public int $serverDiskUsageNotificationThreshold = 50;
+ #[Validate(['required', 'integer', 'min:1', 'max:99'])]
+ public ?int $serverDiskUsageNotificationThreshold = 50;
- #[Validate(['integer', 'min:1'])]
- public int $concurrentBuilds = 1;
+ #[Validate(['required', 'integer', 'min:1'])]
+ public ?int $concurrentBuilds = 1;
- #[Validate(['integer', 'min:1'])]
- public int $dynamicTimeout = 1;
+ #[Validate(['required', 'integer', 'min:1'])]
+ public ?int $dynamicTimeout = 1;
- #[Validate(['integer', 'min:1'])]
- public int $deploymentQueueLimit = 25;
+ #[Validate(['required', 'integer', 'min:1'])]
+ public ?int $deploymentQueueLimit = 25;
public function mount(string $server_uuid)
{
diff --git a/resources/views/livewire/server/advanced.blade.php b/resources/views/livewire/server/advanced.blade.php
index 33086aea1..f6610c1d5 100644
--- a/resources/views/livewire/server/advanced.blade.php
+++ b/resources/views/livewire/server/advanced.blade.php
@@ -22,6 +22,7 @@
id="serverDiskUsageCheckFrequency" label="Disk usage check frequency" required
helper="Cron expression for disk usage check frequency.
You can use every_minute, hourly, daily, weekly, monthly, yearly.
Default is every night at 11:00 PM." />