fix(database): enforce credential format validation and sanitize init/SSL arguments

Add ValidationPatterns helpers for database identifiers and passwords,
apply them across database Livewire components and the API controller,
encode MongoDB init script values via json_encode, and pass the MySQL
user through escapeshellarg when generating SSL chown commands.
This commit is contained in:
Andras Bacsai committed 2026-04-20 13:58:36 +02:00
1 parent 2264a2ef76
commit 03313e54cc
14 files changed
+502 -82

No files matched your search

@@ -76,8 +76,8 @@ class General extends Component
return [
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'clickhouseAdminUser' => 'required|string',
'clickhouseAdminPassword' => 'required|string',
'clickhouseAdminUser' => ValidationPatterns::databaseIdentifierRules(),
'clickhouseAdminPassword' => ValidationPatterns::databasePasswordRules(),
'image' => 'required|string',
'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
@@ -96,10 +96,8 @@ class General extends Component
ValidationPatterns::combinedMessages(),
ValidationPatterns::portMappingMessages(),
[
'clickhouseAdminUser.required' => 'The Admin User field is required.',
'clickhouseAdminUser.string' => 'The Admin User must be a string.',
'clickhouseAdminPassword.required' => 'The Admin Password field is required.',
'clickhouseAdminPassword.string' => 'The Admin Password must be a string.',
...ValidationPatterns::databaseIdentifierMessages('clickhouseAdminUser', 'Admin User'),
...ValidationPatterns::databasePasswordMessages('clickhouseAdminPassword', 'Admin Password'),
'image.required' => 'The Docker Image field is required.',
'image.string' => 'The Docker Image must be a string.',
'publicPort.integer' => 'The Public Port must be an integer.',
@@ -89,7 +89,7 @@ class General extends Component
return [
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'dragonflyPassword' => 'required|string',
'dragonflyPassword' => ValidationPatterns::databasePasswordRules(),
'image' => 'required|string',
'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
@@ -109,8 +109,7 @@ class General extends Component
ValidationPatterns::combinedMessages(),
ValidationPatterns::portMappingMessages(),
[
'dragonflyPassword.required' => 'The Dragonfly Password field is required.',
'dragonflyPassword.string' => 'The Dragonfly Password must be a string.',
...ValidationPatterns::databasePasswordMessages('dragonflyPassword', 'Dragonfly Password'),
'image.required' => 'The Docker Image field is required.',
'image.string' => 'The Docker Image must be a string.',
'publicPort.integer' => 'The Public Port must be an integer.',
@@ -92,7 +92,7 @@ class General extends Component
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'keydbConf' => 'nullable|string',
'keydbPassword' => 'required|string',
'keydbPassword' => ValidationPatterns::databasePasswordRules(),
'image' => 'required|string',
'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
@@ -114,8 +114,7 @@ class General extends Component
ValidationPatterns::combinedMessages(),
ValidationPatterns::portMappingMessages(),
[
'keydbPassword.required' => 'The KeyDB Password field is required.',
'keydbPassword.string' => 'The KeyDB Password must be a string.',
...ValidationPatterns::databasePasswordMessages('keydbPassword', 'KeyDB Password'),
'image.required' => 'The Docker Image field is required.',
'image.string' => 'The Docker Image must be a string.',
'publicPort.integer' => 'The Public Port must be an integer.',
@@ -74,10 +74,10 @@ class General extends Component
return [
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mariadbRootPassword' => 'required',
'mariadbUser' => 'required',
'mariadbPassword' => 'required',
'mariadbDatabase' => 'required',
'mariadbRootPassword' => ValidationPatterns::databasePasswordRules(),
'mariadbUser' => ValidationPatterns::databaseIdentifierRules(),
'mariadbPassword' => ValidationPatterns::databasePasswordRules(),
'mariadbDatabase' => ValidationPatterns::databaseIdentifierRules(),
'mariadbConf' => 'nullable',
'image' => 'required',
'portsMappings' => ValidationPatterns::portMappingRules(),
@@ -97,10 +97,10 @@ class General extends Component
ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mariadbRootPassword.required' => 'The Root Password field is required.',
'mariadbUser.required' => 'The MariaDB User field is required.',
'mariadbPassword.required' => 'The MariaDB Password field is required.',
'mariadbDatabase.required' => 'The MariaDB Database field is required.',
...ValidationPatterns::databasePasswordMessages('mariadbRootPassword', 'Root Password'),
...ValidationPatterns::databaseIdentifierMessages('mariadbUser', 'MariaDB User'),
...ValidationPatterns::databasePasswordMessages('mariadbPassword', 'MariaDB Password'),
...ValidationPatterns::databaseIdentifierMessages('mariadbDatabase', 'MariaDB Database'),
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'publicPort.min' => 'The Public Port must be at least 1.',
@@ -75,9 +75,9 @@ class General extends Component
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mongoConf' => 'nullable',
'mongoInitdbRootUsername' => 'required',
'mongoInitdbRootPassword' => 'required',
'mongoInitdbDatabase' => 'required',
'mongoInitdbRootUsername' => ValidationPatterns::databaseIdentifierRules(),
'mongoInitdbRootPassword' => ValidationPatterns::databasePasswordRules(),
'mongoInitdbDatabase' => ValidationPatterns::databaseIdentifierRules(),
'image' => 'required',
'portsMappings' => ValidationPatterns::portMappingRules(),
'isPublic' => 'nullable|boolean',
@@ -97,9 +97,9 @@ class General extends Component
ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
'mongoInitdbRootPassword.required' => 'The Root Password field is required.',
'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.',
...ValidationPatterns::databaseIdentifierMessages('mongoInitdbRootUsername', 'Root Username'),
...ValidationPatterns::databasePasswordMessages('mongoInitdbRootPassword', 'Root Password'),
...ValidationPatterns::databaseIdentifierMessages('mongoInitdbDatabase', 'MongoDB Database'),
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'publicPort.min' => 'The Public Port must be at least 1.',
@@ -76,10 +76,10 @@ class General extends Component
return [
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mysqlRootPassword' => 'required',
'mysqlUser' => 'required',
'mysqlPassword' => 'required',
'mysqlDatabase' => 'required',
'mysqlRootPassword' => ValidationPatterns::databasePasswordRules(),
'mysqlUser' => ValidationPatterns::databaseIdentifierRules(),
'mysqlPassword' => ValidationPatterns::databasePasswordRules(),
'mysqlDatabase' => ValidationPatterns::databaseIdentifierRules(),
'mysqlConf' => 'nullable',
'image' => 'required',
'portsMappings' => ValidationPatterns::portMappingRules(),
@@ -100,10 +100,10 @@ class General extends Component
ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'mysqlRootPassword.required' => 'The Root Password field is required.',
'mysqlUser.required' => 'The MySQL User field is required.',
'mysqlPassword.required' => 'The MySQL Password field is required.',
'mysqlDatabase.required' => 'The MySQL Database field is required.',
...ValidationPatterns::databasePasswordMessages('mysqlRootPassword', 'Root Password'),
...ValidationPatterns::databaseIdentifierMessages('mysqlUser', 'MySQL User'),
...ValidationPatterns::databasePasswordMessages('mysqlPassword', 'MySQL Password'),
...ValidationPatterns::databaseIdentifierMessages('mysqlDatabase', 'MySQL Database'),
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'publicPort.min' => 'The Public Port must be at least 1.',
@@ -86,9 +86,9 @@ class General extends Component
return [
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'postgresUser' => 'required',
'postgresPassword' => 'required',
'postgresDb' => 'required',
'postgresUser' => ValidationPatterns::databaseIdentifierRules(),
'postgresPassword' => ValidationPatterns::databasePasswordRules(),
'postgresDb' => ValidationPatterns::databaseIdentifierRules(),
'postgresInitdbArgs' => 'nullable',
'postgresHostAuthMethod' => 'nullable',
'postgresConf' => 'nullable',
@@ -112,9 +112,9 @@ class General extends Component
ValidationPatterns::portMappingMessages(),
[
'name.required' => 'The Name field is required.',
'postgresUser.required' => 'The Postgres User field is required.',
'postgresPassword.required' => 'The Postgres Password field is required.',
'postgresDb.required' => 'The Postgres Database field is required.',
...ValidationPatterns::databaseIdentifierMessages('postgresUser', 'Postgres User'),
...ValidationPatterns::databasePasswordMessages('postgresPassword', 'Postgres Password'),
...ValidationPatterns::databaseIdentifierMessages('postgresDb', 'Postgres Database'),
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'publicPort.min' => 'The Public Port must be at least 1.',
@@ -81,8 +81,8 @@ class General extends Component
'publicPortTimeout' => 'nullable|integer|min:1',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'redisUsername' => 'required',
'redisPassword' => 'required',
'redisUsername' => ValidationPatterns::databaseIdentifierRules(),
'redisPassword' => ValidationPatterns::databasePasswordRules(),
'enableSsl' => 'boolean',
];
}
@@ -100,8 +100,8 @@ class General extends Component
'publicPort.max' => 'The Public Port must not exceed 65535.',
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
'redisUsername.required' => 'The Redis Username field is required.',
'redisPassword.required' => 'The Redis Password field is required.',
...ValidationPatterns::databaseIdentifierMessages('redisUsername', 'Redis Username'),
...ValidationPatterns::databasePasswordMessages('redisPassword', 'Redis Password'),
]
);
}