Skip to content

Coolify RCE via LocalFileVolume `fs_path`

High
andrasbacsai published GHSA-46hp-7m8g-7622 Jul 2, 2026

Package

composer coollabsio/coolify (Composer)

Affected versions

<= 4.0.0-beta.470

Patched versions

4.0.0-beta.471

Description

Summary

The fix for CVE-2025-66213 added validateShellSafePath and escapeshellarg only for path uses that occur after building the initial command list in LocalFileVolume::saveStorageOnServer(). The vulnerable interpolations are on lines 132 and 139, where $this->fs_path (and $parent_dir derived from it) are pushed into shell commands without escape. Those commands are then sent verbatim via instant_remote_process() over SSH.

Additionally, the file mount flow (submitFileStorage()) does not call validateShellSafePath on the user-controlled path before creating the volume. On save, the model's booted() dispatches ServerStorageSaveJob, which calls saveStorageOnServer() — so RCE is achievable on save, without any deploy.

Affected Versions

< v4.x @ 89aecc2 (confirmed) >

Root Cause

1) Commands built with unescaped fs_path before any hardening

In app/Models/LocalFileVolume.php, saveStorageOnServer() builds $commands as follows:

  • Lines 132–134:
    $commands->push("mkdir -p $this->fs_path ...");
    $commands->push("mkdir -p $workdir ...");
    $commands->push("cd $workdir");
  • Line 139:
    $commands->push("mkdir -p $parent_dir ...");
    where $parent_dir is derived from $this->fs_path (line 136).

All of these interpolate user-controlled or path-derived values without escapeshellarg or validation at this point.

2) Validation and escape happen only later, for different uses

At lines 151–152 the code does:

  • validateShellSafePath($path, 'storage path');
  • $escapedPath = escapeshellarg($path);

$path here is the transformed path (e.g. with workdir prepended). This $escapedPath is used only for subsequent commands (test -f, cat, tee, touch, mkdir -p {$escapedPath}, etc.). The commands already pushed at 132 and 139 are never rewritten with the escaped value; they still contain raw $this->fs_path and $parent_dir.

3) Commands sent verbatim over SSH

At line 196, instant_remote_process($commands, $server) sends the full $commands array to the remote server. The first elements of $commands therefore execute with unescaped fs_path in the shell, enabling command injection if fs_path contains metacharacters (e.g. ;, |, $(...)).

4) File mount path not validated on submit

In app/Livewire/Project/Service/Storage.php:

  • submitFileStorageDirectory() (lines 183–184) calls validateShellSafePath() on source and destination before creating the volume.
  • submitFileStorage() (lines 128–164) only validates 'file_storage_path' => 'required|string' and does not call validateShellSafePath for the file-mount path.

The file path is built as $fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path (or equivalent for standalone). The user-controlled suffix (file_storage_path) is stored as fs_path on LocalFileVolume and is then used in the vulnerable command construction in saveStorageOnServer().

5) RCE on save, no deploy required

LocalFileVolume::booted() (lines 26–29) dispatches ServerStorageSaveJob on created. The job's handle() calls saveStorageOnServer(). So creating a new file mount via the UI (submitFileStorage) immediately triggers remote execution of the built commands, including the unescaped fs_path — RCE on save, without any deployment step.

Attack Scenario

  1. Attacker has permission to add file storage to an application or standalone service (e.g. “File” mount in Storage UI).
  2. Attacker submits a path suffix containing shell metacharacters (e.g. /x; id > /tmp/pwned # or $(curl ...)).
  3. submitFileStorage() builds fs_path and creates LocalFileVolume without validateShellSafePath.
  4. ServerStorageSaveJob runs and calls saveStorageOnServer().
  5. Commands at lines 132 and 139 are built with the malicious fs_path and executed via instant_remote_process() on the target server → arbitrary command execution.

Impact

  • Remote command execution on the server that runs Coolify’s queue and SSH (e.g. Coolify host or deployment target), in the context of instant_remote_process().
  • Potential full compromise of deployment infrastructure, credential theft, and lateral movement.

Suggested Fix

  1. Use escaped path for all shell uses of fs_path in saveStorageOnServer()
    • Compute a single normalized path (including workdir logic), run validateShellSafePath() and escapeshellarg() on it once, and use this escaped value in every command that includes the path (including the initial mkdir -p and mkdir -p $parent_dir at lines 132 and 139). Do not push commands that interpolate raw $this->fs_path or $parent_dir.
  2. Validate file-mount path at submit
    • In submitFileStorage(), after deriving the full path (or the user-controlled segment), call validateShellSafePath() before calling LocalFileVolume::create(), consistent with submitFileStorageDirectory().
  3. Regression tests
    • Add tests that assert metacharacter payloads in fs_path (and in the file-mount path input) never reach shell execution unescaped, for both file and directory mount flows and for create/update/save paths.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE ID

CVE-2026-34153

Weaknesses

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. Learn more on MITRE.

Credits