Merge pull request #6891 from coollabsio/fix-compose-volume-injection

fix: docker compose parsing
This commit is contained in:
Andras Bacsai authored and GitHub committed 2025-10-16 10:08:11 +02:00
commit 4783dcb80a
18 files changed
+1843 -13

No files matched your search

@@ -328,9 +328,23 @@ class ServicesController extends Controller
});
}
if ($oneClickService) {
$service_payload = [
$dockerComposeRaw = base64_decode($oneClickService);
// Validate for command injection BEFORE creating service
try {
validateDockerComposeForInjection($dockerComposeRaw);
} catch (\Exception $e) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'docker_compose_raw' => $e->getMessage(),
],
], 422);
}
$servicePayload = [
'name' => "$oneClickServiceName-".str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'docker_compose_raw' => $dockerComposeRaw,
'environment_id' => $environment->id,
'service_type' => $oneClickServiceName,
'server_id' => $server->id,
@@ -338,9 +352,9 @@ class ServicesController extends Controller
'destination_type' => $destination->getMorphClass(),
];
if ($oneClickServiceName === 'cloudflared') {
data_set($service_payload, 'connect_to_docker_network', true);
data_set($servicePayload, 'connect_to_docker_network', true);
}
$service = Service::create($service_payload);
$service = Service::create($servicePayload);
$service->name = "$oneClickServiceName-".$service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
@@ -462,6 +476,18 @@ class ServicesController extends Controller
$dockerCompose = base64_decode($request->docker_compose_raw);
$dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
// Validate for command injection BEFORE saving to database
try {
validateDockerComposeForInjection($dockerComposeRaw);
} catch (\Exception $e) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'docker_compose_raw' => $e->getMessage(),
],
], 422);
}
$connectToDockerNetwork = $request->connect_to_docker_network ?? false;
$instantDeploy = $request->instant_deploy ?? false;
@@ -777,6 +803,19 @@ class ServicesController extends Controller
}
$dockerCompose = base64_decode($request->docker_compose_raw);
$dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
// Validate for command injection BEFORE saving to database
try {
validateDockerComposeForInjection($dockerComposeRaw);
} catch (\Exception $e) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'docker_compose_raw' => $e->getMessage(),
],
], 422);
}
$service->docker_compose_raw = $dockerComposeRaw;
}
@@ -37,6 +37,10 @@ class DockerCompose extends Component
'dockerComposeRaw' => 'required',
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
// Validate for command injection BEFORE saving to database
validateDockerComposeForInjection($this->dockerComposeRaw);
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
@@ -101,6 +101,10 @@ class StackForm extends Component
{
try {
$this->validate();
// Validate for command injection BEFORE saving to database
validateDockerComposeForInjection($this->service->docker_compose_raw);
$this->service->save();
$this->service->saveExtraFields($this->fields);
$this->service->parse();
+3 -3
View File
@@ -1109,7 +1109,7 @@ class Application extends BaseModel
// When used with executeInDocker (which uses bash -c '...'), we need to escape for bash context
// Replace ' with '\'' to safely escape within single-quoted bash strings
$escapedCustomRepository = str_replace("'", "'\\''", $customRepository);
$base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} '{$escapedCustomRepository}'";
$base_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} '{$escapedCustomRepository}'";
if ($exec_in_docker) {
$commands = collect([
@@ -1126,9 +1126,9 @@ class Application extends BaseModel
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $base_comamnd));
$commands->push(executeInDocker($deployment_uuid, $base_command));
} else {
$commands->push($base_comamnd);
$commands->push($base_command);
}
return [
+5 -5
View File
@@ -79,11 +79,11 @@ class ServerSetting extends Model
});
static::updated(function ($settings) {
if (
$settings->isDirty('sentinel_token') ||
$settings->isDirty('sentinel_custom_url') ||
$settings->isDirty('sentinel_metrics_refresh_rate_seconds') ||
$settings->isDirty('sentinel_metrics_history_days') ||
$settings->isDirty('sentinel_push_interval_seconds')
$settings->wasChanged('sentinel_token') ||
$settings->wasChanged('sentinel_custom_url') ||
$settings->wasChanged('sentinel_metrics_refresh_rate_seconds') ||
$settings->wasChanged('sentinel_metrics_history_days') ||
$settings->wasChanged('sentinel_push_interval_seconds')
) {
$settings->server->restartSentinel();
}
+1 -1
View File
@@ -26,7 +26,7 @@ trait DeletesUserSessions
{
static::updated(function ($user) {
// Check if password was changed
if ($user->isDirty('password')) {
if ($user->wasChanged('password')) {
$user->deleteAllSessions();
}
});