Skip to content

CVE-2026-34594: Verified Repro With Script Download

CVE-2026-34594: Coolify authenticated command injection in Destination Network Management

CVE-2026-34594 is verified against the affected target. Vulnerability class: Command Injection. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00245.

REPRO-2026-00245 Command Injection Variant found Jul 6, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.8
Confidence
HIGH
Reproduced in
50m 51s
Tool calls
313
Spend
$7.51
01 · Overview

What Is CVE-2026-34594?

CVE-2026-34594 is a high-severity authenticated OS command injection (CWE-78) in Coolify's Destination Network Management feature, letting a user with destination-management permissions execute arbitrary commands as root on the Coolify-managed host. Pruva reproduced it (reproduction REPRO-2026-00245).

02 · Severity & CVSS

CVE-2026-34594 Severity & CVSS Score

CVE-2026-34594 is rated high severity, with a CVSS base score of 8.8 out of 10.

HIGH threat level
8.8 / 10 CVSS base

How to Reproduce CVE-2026-34594

$ pruva-verify REPRO-2026-00245
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00245/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-34594

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Destination Network Management 'network' field = x;id > /tmp/coolify_net_pwned 2>&1 # (shell metacharacters ; > #)

Attack chain
  1. Authenticated POST /livewire/update driving destination.new.docker submit()
  2. StandaloneDocker::create(['network'=>...])
  3. StandaloneDocker::created boot event
  4. instant_remote_process(['docker network inspect <network> ...'],$server)
  5. SshMultiplexingHelper::generateSshCommand
  6. ssh root@server 'bash -se' << delim (unsanitized network executed by remote bash)
Runnable proof: reproduction_steps.sh
Captured evidence: vulnerable attempt 1fixed attempt 1vulnerable attempt 2fixed attempt 2vulnerable attempt 1vulnerable attempt 2fixed attempt 1fixed attempt 2
Variants tested

Alternate-trigger variant of CVE-2026-34594: the same authenticated command-injection root cause (attacker-controlled Docker network name interpolated unsanitized into the SSH bash -se here-doc via StandaloneDocker::created -> instant_remote_process) is reachable through a different Livewire component, App\Livewire\Se…

How the agent worked 749 events · 313 tool calls · 51 min
51 minDuration
313Tool calls
189Reasoning steps
749Events
3Dead-ends
Agent activity over 51 min
Support
21
Repro
449
Judge
19
Variant
256
0:0050:51

Root Cause and Exploit Chain for CVE-2026-34594

Versions: Coolify < 4.0.0-beta.471 (verified on the official

Coolify (self-hosted PaaS, Laravel/Livewire application) before 4.0.0-beta.471 contains an authenticated command-injection vulnerability in the Destination Network Management functionality. The network field of a Docker Destination (App\Livewire\Destination\New\Docker and App\Livewire\Destination\Show) was only validated as ['required','string'], so an authenticated user with destination-management permissions could submit a value containing shell metacharacters. That value is stored on the StandaloneDocker / SwarmDocker model and later interpolated unsanitized into shell commands that Coolify executes on the destination's server over SSH (instant_remote_process()SshMultiplexingHelper::generateSshCommand()ssh ... 'bash -se' << $delimiter). Because the network string becomes the body of a here-doc fed to bash -se on the remote host, an attacker-controlled network such as x;id > /tmp/pwned # results in arbitrary command execution as root on the Coolify-managed server at the moment the destination is created (the StandaloneDocker::created boot event) and again when it is deleted.

  • Package/component affected: coollabsio/coolify — Destination Network Management (app/Livewire/Destination/New/Docker.php, app/Livewire/Destination/Show.php, app/Models/StandaloneDocker.php, app/Models/SwarmDocker.php, and every shell consumer of ->destination->network: ApplicationDeploymentJob, DatabaseBackupJob, StartService, Init command, bootstrap/helpers/proxy.php).
  • Affected versions: Coolify < 4.0.0-beta.471 (verified on the official image ghcr.io/coollabsio/coolify:4.0.0-beta.470).
  • Risk level: High. Coolify executes SSH commands with the destination server's user (default root). The injected command runs with that privilege on the managed host, enabling full server compromise, access to the Docker socket, lateral movement, and exposure of all managed resources / secrets. Exploitation requires only an authenticated user who can create a destination (the StandaloneDockerPolicy::create check returns true for any authenticated user; createAnyResource likewise returns true).

Impact Parity

  • Disclosed/claimed maximum impact: Authenticated command execution ("arbitrary command execution on the Coolify host") via the Destination Network Management network field.
  • Reproduced impact from this run: Authenticated command execution as root on the destination server. The injected id command wrote uid=0(root) gid=0(root) groups=0(root) to a marker file on the managed server, proving arbitrary command execution with root privileges through the authenticated web request.
  • Parity: full — the claimed authenticated command injection was demonstrated end-to-end against the real product, and the fixed build rejects the same input with no execution.

Root Cause

The network attribute of a Docker Destination is attacker-controlled input that flows into privileged shell commands without validation or escaping.

Vulnerable code path (beta.470):

  1. app/Livewire/Destination/New/Docker.php declares the field with only #[Validate(['required','string'])] (no character whitelist), and submit() calls StandaloneDocker::create(['network' => $this->network, ...]).
  2. app/Models/StandaloneDocker.php boot() registers a static::created hook that runs:
    instant_remote_process([
        "docker network inspect $newStandaloneDocker->network >/dev/null 2>&1 || docker network create --driver overlay --attachable $newStandaloneDocker->network >/dev/null",
    ], $server, false);
    
    The network is interpolated directly into the command string.
  3. instant_remote_process() builds an SSH command via SshMultiplexingHelper::generateSshCommand(), which sends the command as the body of a here-doc to bash -se on the remote server:
    timeout <t> ssh ... 'root'@'<ip>' 'bash -se' << $delimiter
    <command containing the unsanitized network>
    $delimiter
    
    escapedUserAtHost() escapes user@ip, but the command body (the network) is never escaped, so shell metacharacters in network are interpreted by the remote bash. ; breaks out of the docker network inspect token, the attacker command runs, and # comments out the rest of the line. The same unsanitized interpolation exists in Destination/Show.php::delete() and across ApplicationDeploymentJob, DatabaseBackupJob, StartService, Init, and proxy.php.

Fix (PR #9228, released in 4.0.0-beta.471, commit 3d1b9f53a0aec74468be75675bcaaaed0fd41d46; release tag commit 914d7e0b50505bc1fd56c34974fca09ad354e92a):

  • Adds DOCKER_NETWORK_PATTERN = '/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/' in app/Support/ValidationPatterns.php and applies it as a regex validation rule on the network field in both Livewire components.
  • Adds a setNetworkAttribute() mutator on StandaloneDocker and SwarmDocker that throws InvalidArgumentException for names not matching the pattern.
  • Applies escapeshellarg() to every shell usage of the network field.

Reproduction Steps

  1. Reference: bundle/repro/reproduction_steps.sh (self-contained).
  2. What the script does:
    • Installs the docker compose v2 plugin if missing and pulls the official Coolify images (4.0.0-beta.470 vulnerable, 4.0.0-beta.471 fixed) plus postgres:15-alpine, redis:7-alpine, coolify-testing-host (an SSH-enabled target whose authorized_keys matches a hardcoded key) and coolify-realtime.
    • Brings up the real Coolify stack (Coolify Laravel app + Postgres + Redis + Soketi + the SSH target) on a private Docker network, with IS_WINDOWS_DOCKER_DESKTOP=true so the seeded localhost server targets coolify-testing-host.
    • Bootstraps an authenticated root user, the testing-host SSH private key, and a server pointing at coolify-testing-host (root, port 22).
    • From the coolify-testing-host container, runs an authenticated Python client against the live Coolify web app (http://coolify:8080): logs in via Laravel Fortify, loads /server/{uuid}/destinations, extracts the destination.new.docker Livewire snapshot, and POSTs /livewire/update setting network = "x;id > /tmp/coolify_net_pwned 2>&1 #" and calling submit().
    • Repeats for the fixed image as a negative control.
  3. Expected evidence:
    • Vulnerable build: the StandaloneDocker::created event runs the SSH command with the injected network; /tmp/coolify_net_pwned* on the SSH target contains uid=0(root) gid=0(root) groups=0(root), and the Livewire response redirects to the newly created destination.
    • Fixed build: the request is rejected with The network field format is invalid. (regex validation), no destination is created, and no marker file appears.

Evidence

  • bundle/logs/reproduction_steps.log — full orchestrated run log.
  • bundle/logs/stack_vulnerable.log, bundle/logs/setup_vulnerable.log — vulnerable stack bring-up and bootstrap.
  • bundle/logs/vulnerable_attempt_1.log, bundle/logs/vulnerable_attempt_2.log — exploit output for the vulnerable build (Livewire redirect to /destination/<uuid> → destination created).
  • bundle/logs/vulnerable_marker_content.txt — captured marker content (uid=0(root) gid=0(root) groups=0(root)).
  • bundle/logs/stack_fixed.log, bundle/logs/setup_fixed.log — fixed stack.
  • bundle/logs/fixed_attempt_1.log, bundle/logs/fixed_attempt_2.log — exploit output for the fixed build (VALIDATION_BLOCKED + The network field format is invalid., no redirect, no marker).
  • bundle/repro/runtime_manifest.json — runtime evidence manifest (entrypoint_kind=api_remote, service_started=true, healthcheck_passed=true, target_path_reached=true).

Key reproduced excerpts (from the interactive verification run):

  • Vulnerable: effects redirect=http://coolify:8080/destination/o1jvtyfefdzb8nxnyqv0lkd2; standalone_dockers row with network = x;id > /tmp/coolify_net_pwned 2>&1 # on server_id=1; marker /tmp/coolify_net_pwned = uid=0(root) gid=0(root) groups=0(root).
  • Fixed: effects redirect=None, response snippet The network field format is invalid.; no standalone_dockers row with the malicious network; marker file absent.

Environment: official Coolify Docker images on the shared Docker daemon; the authenticated HTTP client runs in the coolify-testing-host container (reaches coolify:8080 over the coolifynet-repro bridge); command execution is performed by Coolify over SSH (bash -se here-doc) from the coolify container to coolify-testing-host as root.

Recommendations / Next Steps

  • Upgrade to Coolify >= 4.0.0-beta.471 (PR #9228). The fix combines input validation (regex whitelist matching Docker network naming rules), a model mutator that rejects invalid names, and escapeshellarg() on every shell usage of the network field.
  • Defense-in-depth: audit every instant_remote_process() / remote_process() call site for user-controlled interpolation; prefer escapeshellarg() / escapeshellcmd() and validated whitelists for any field that becomes part of an SSH/Docker command string. Consider a shared helper that forbids raw string interpolation of model attributes into command strings.
  • Restrict destination-management permissions; do not expose the Coolify admin interface publicly.

Additional Notes

  • Idempotency: the script tears the stack down (docker compose down -v + hard cleanup of the fixed container/volume/network names) before each build, and setup.php is idempotent (locates the root user by email, fixes the password hash, ensures a single coolify-testing-host server). Two attempts are run per build with distinct marker paths (hence distinct network strings) to avoid the (server_id, network) unique constraint.
  • The Docker daemon in this environment is on a separate host from the script execution sandbox, so published host ports are not reachable from the sandbox; the authenticated client therefore runs inside the coolify-testing-host container and reaches Coolify over the private Docker network (http://coolify:8080). This is a genuine authenticated HTTP request to the running Coolify service; the command execution crosses a real SSH boundary from Coolify to the managed server.
  • No sanitizers (ASAN/UBSAN/etc.) are used; this is a real product-mode runtime proof. The negative control (fixed image) demonstrates the patch closes the injection without relying on sanitizer output.

Variant Analysis & Alternative Triggers for CVE-2026-34594

Versions: versions (as tested): confirmed on official image

A materially distinct alternate-trigger variant of the Coolify Destination Network command-injection (CVE-2026-34594) was found and confirmed at runtime. The fix shipped in PR #9228 (4.0.0-beta.471) added regex validation to the network Livewire property of the Destination\New\Docker and Destination\Show components, plus a setNetworkAttribute model mutator and escapeshellarg() on every shell consumer. However, the same root cause (unsanitized, attacker-controlled Docker network name interpolated into the SSH bash -se here-doc via StandaloneDocker::createdinstant_remote_process) is reachable through a different Livewire component that PR #9228 did not touch: App\Livewire\Server\Destinations::add($name). That method takes the network name as a method argument (not a #[Validate]-annotated property) and calls StandaloneDocker::create(['network' => $name, …]) with no validation of its own.

  • On the vulnerable build (4.0.0-beta.470) the variant reproduces end-to-end: add("x;id > /tmp/marker #") executes id as root on the managed server (marker = uid=0(root) gid=0(root) groups=0(root)), 2/2 attempts.
  • On the fixed build (4.0.0-beta.471) the variant is blocked by the StandaloneDocker::setNetworkAttribute() model mutator, which throws InvalidArgumentException ("Invalid Docker network name…") before the created event runs (confirmed in laravel.log: exception at app/Models/StandaloneDocker.php:37 originating from app/Livewire/Server/Destinations.php:60add()). No marker, no DB row.

This is therefore an alternate trigger, not a bypass: it reaches the same sink from a different entry point and is stopped only by the fix's defense-in-depth model mutator, not by the fix's primary (regex-validation) layer — which was never applied to Server\Destinations::add().

Fix Coverage / Assumptions

PR #9228 (merge 9e96a20a49f32e46d7a31e66e1aa6338b029f284, branch commit 3d1b9f53a0aec74468be75675bcaaaed0fd41d46, released in 4.0.0-beta.471) relies on three assumptions:

  1. Every user-controlled network value enters through a #[Validate]-annotated $network Livewire property. The fix only added regex:/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/ to Destination\New\Docker::$network and Destination\Show::$network. It assumes there is no other component that accepts a network name from the user.
  2. The model mutator is a universal backstop. setNetworkAttribute() on StandaloneDocker/SwarmDocker throws on any invalid name, so any ::create(['network' => …]) is assumed to be caught even if a component skips property validation.
  3. escapeshellarg() on every shell consumer neutralizes any value that somehow reaches the sink.

Paths explicitly covered: destination.new.docker.submit(), destination.show.submit()/delete(), StandaloneDocker::created, ApplicationDeploymentJob, DatabaseBackupJob, StartService, Init, and bootstrap/helpers/proxy.php.

Variant / Alternate Trigger

Entry point: App\Livewire\Server\Destinations (Livewire component name server.destinations, route server.destinations/server/{uuid}/destinations), method add($name).

// app/Livewire/Server/Destinations.php  (UNCHANGED by PR #9228)
public function add($name)
{
    if ($this->server->isSwarm()) {
        … SwarmDocker::create([ 'name' => …, 'network' => $this->name, … ]); // pre-existing bug
    } else {
        $this->authorize('create', StandaloneDocker::class);
        $found = $this->server->standaloneDockers()->where('network', $name)->first();
        if ($found) { $this->dispatch('error', 'Network already added…'); return; }
        StandaloneDocker::create([
            'name' => $this->server->name.'-'.$name,
            'network' => $name,                 // <-- attacker-controlled method ARGUMENT
            'server_id' => $this->server->id,
        ]);
    }
    $this->createNetworkAndAttachToProxy();
}

The intended UI flow is "Scan for Destinations" → "Add <discovered network>" (wire:click="add('{{ data_get($network, 'Name') }}')"), where $name is a real Docker network name. But Livewire method arguments are not constrained to rendered values: an authenticated user can POST /livewire/update directly and call add with an arbitrary string. $name is a method parameter, so the #[Validate] property rules added by the fix do not apply to it.

Trigger path (vulnerable): POST /livewire/update → hydrate server.destinationsadd("x;id > /tmp/marker #")$this->authorize('create', StandaloneDocker::class) (passes for any authenticated user) → StandaloneDocker::create(['network' => "x;id … #"])StandaloneDocker::created boot event → instant_remote_process(["docker network inspect x;id > /tmp/marker # …"], $server)SshMultiplexingHelper::generateSshCommandssh root@<host> 'bash -se' << delim → remote bash executes id > /tmp/marker as root. Same sink, same root cause, different component and input vector.

Why it is not a bypass (fixed build): StandaloneDocker::create(['network' => $name]) invokes setNetworkAttribute($name) (Eloquent mutator via fill()/setAttribute(); network is in $fillable). The mutator throws InvalidArgumentException for names not matching /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/. The exception propagates out of add() (no try/catch) to Livewire → HTTP 500; the created event never fires and no command runs. Confirmed in laravel.log: InvalidArgumentException … at app/Models/StandaloneDocker.php:37 with stack frame app/Livewire/Server/Destinations.php(60) … ->add().

  • Package/component affected: coollabsio/coolifyapp/Livewire/Server/Destinations.php (server.destinations component, add() method), reaching the shared StandaloneDocker::created sink, instant_remote_process, and SshMultiplexingHelper.
  • Affected versions (as tested): confirmed on official image ghcr.io/coollabsio/coolify:4.0.0-beta.470 (tag commit 575b0766d12bad2a78febff72ab59c017772bcf7); blocked on ghcr.io/coollabsio/coolify:4.0.0-beta.471 (tag commit 914d7e0b50505bc1fd56c34974fca09ad354e92a, fix merge 9e96a20a49f32e46d7a31e66e1aa6338b029f284).
  • Risk level: High (parity with the parent CVE). Authenticated user with destination-management permissions → arbitrary command execution as root on the Coolify-managed server over SSH, at destination-creation time. The StandaloneDockerPolicy::create check returns true for any authenticated user, so any logged-in user can reach this.

Impact Parity

  • Disclosed/claimed maximum impact (parent): authenticated arbitrary command execution on the Coolify host/managed server via the Destination Network Management network field.
  • Reproduced impact from this variant run: authenticated command execution as root on the destination server through the server.destinations.add($name) entry point — marker files contain uid=0(root) gid=0(root) groups=0(root) (2/2 attempts on beta.470).
  • Parity: full — the same root-cause primitive (root command execution via the SSH here-doc) is reached from a different entry point with the same attacker capability (authenticated destination-management user).
  • Not demonstrated: persistence / post-exploitation beyond the id proof marker; the fixed build's behavior (clean rejection vs. unhandled 500) is characterized but no further exploitation was attempted.

Root Cause

The network attribute of a Docker Destination is attacker-controlled input that, in the vulnerable code, is interpolated unsanitized into shell commands executed on the destination server over SSH (instant_remote_process()SshMultiplexingHelper::generateSshCommand()ssh root@<host> 'bash -se' << $delim). Because the network becomes the body of a here-doc fed to bash -se, shell metacharacters (;, #, $(), backticks) are interpreted by the remote bash. PR #9228 closed this for the Destination\New\Docker and Destination\Show components and added a model mutator + escapeshellarg() as defense in depth.

The same underlying bug is still reachable on the vulnerable build through Server\Destinations::add($name): $name is a Livewire method argument (not a validated property), so the fix's regex-validation layer never sees it, and on beta.470 there is no mutator and no escapeshellarg() on the created-event command. On beta.471 the model mutator (setNetworkAttribute) catches it — which is why this is an alternate trigger rather than a bypass.

Fix commit: 3d1b9f53a0aec74468be75675bcaaaed0fd41d46 (merge 9e96a20a49f32e46d7a31e66e1aa6338b029f284, PR #9228).

Reproduction Steps

  1. Reference: bundle/vuln_variant/reproduction_steps.sh (self-contained, idempotent).
  2. What the script does:
    • Installs the docker compose v2 plugin if missing; pulls the official Coolify images (4.0.0-beta.470 vulnerable, 4.0.0-beta.471 fixed) plus postgres:15-alpine, redis:7-alpine, coolify-testing-host, coolify-realtime.
    • Brings up the real Coolify stack (Laravel app + Postgres + Redis + Soketi + SSH target) on a private bridge network, with IS_WINDOWS_DOCKER_DESKTOP=true so the seeded localhost server targets coolify-testing-host.
    • Bootstraps an authenticated root user, the testing-host SSH private key, and a usable coolify-testing-host server (idempotent setup.php).
    • From the coolify-testing-host container, runs an authenticated Python client against the live Coolify web app (http://coolify:8080): logs in, loads /server/{uuid}/destinations, extracts the server.destinations Livewire snapshot (deliberately NOT destination.new.docker), and POSTs /livewire/update calling add("x;id > /tmp/coolify_variant_pwned_<tag> 2>&1 #").
    • Repeats 2× on the vulnerable build, then tears down and repeats 2× on the fixed build.
  3. Expected evidence:
    • Vulnerable build: /tmp/coolify_variant_pwned_vuln_* on the SSH target contains uid=0(root) gid=0(root) groups=0(root); Livewire returns 200 and re-renders the destinations page.
    • Fixed build: Livewire returns 500; laravel.log shows InvalidArgumentException: Invalid Docker network name… at app/Models/StandaloneDocker.php:37 from Server/Destinations.php:60 (add()); no marker, no standalone_dockers row with the malicious network.

Evidence

  • bundle/logs/vuln_variant/reproduction_steps.log — full orchestrated run log (both runs).
  • bundle/logs/vuln_variant/stack_vulnerable.log, setup_vulnerable.log — vulnerable stack bring-up and bootstrap.
  • bundle/logs/vuln_variant/vulnerable_attempt_1.log, vulnerable_attempt_2.log — variant exploit output for beta.470 (livewire_update_status=200, snapshot_found name=server.destinations).
  • bundle/logs/vuln_variant/vulnerable_marker_1.txt, vulnerable_marker_2.txt — captured marker content (uid=0(root) gid=0(root) groups=0(root)).
  • bundle/logs/vuln_variant/stack_fixed.log, setup_fixed.log — fixed stack.
  • bundle/logs/vuln_variant/fixed_attempt_1.log, fixed_attempt_2.log — variant exploit output for beta.471 (livewire_update_status=500, no marker).
  • bundle/logs/vuln_variant/debug_fixed_evidence.txtlaravel.log excerpt proving the setNetworkAttribute mutator (StandaloneDocker.php:37) threw from Server/Destinations.php:60 (add()), and that no malicious standalone_dockers row was inserted.
  • bundle/logs/vuln_variant/debug_vulnerable_evidence.txt — interactive debug capture of the vulnerable run (status 200 + uid=0(root) marker).
  • bundle/logs/vuln_variant/run1/ — preserved copy of run #1 logs/manifest.
  • bundle/vuln_variant/runtime_manifest.json — runtime evidence manifest (entrypoint_kind=api_remote, variant_confirmed_on_vulnerable=true, bypass_on_fixed=false).

Environment: official Coolify Docker images on the shared Docker daemon; the authenticated HTTP client runs in the coolify-testing-host container (reaches coolify:8080 over coolifynet-variant); command execution is performed by Coolify over SSH (bash -se here-doc) from the coolify container to coolify-testing-host as root.

Recommendations / Next Steps

  1. Add ValidationPatterns::dockerNetworkRules() validation to Server\Destinations::add($name) (validate $name before ::create, with a friendly dispatch('error', …) mirroring Destination\New\Docker), so invalid input is rejected with a user-facing message instead of an unhandled InvalidArgumentException 500. This closes the surface gap the variant exposes.
  2. Fix the pre-existing swarm-branch bug in add() ('network' => $this->name'network' => $name); on beta.471 the mutator's string type hint turns the current null into a TypeError.
  3. Keep the model mutator and escapeshellarg() as defense in depth (already in place).
  4. Treat Livewire method arguments as untrusted input generally: method parameters bypass #[Validate] property rules, so any component method that forwards a user-supplied argument into a model ::create() or a shell command must validate the argument explicitly. A repo-wide audit of instant_remote_process()/remote_process() call sites for raw model attribute interpolation is recommended.

Additional Notes

  • Idempotency: reproduction_steps.sh was executed twice end-to-end. Both runs completed without crashing (exit 1 = alternate trigger, not bypass, per the stage's exit-code convention) and produced identical verdicts: vulnerable 2/2 markers (uid=0(root)), fixed 0/2 blocked. setup.php is idempotent and deletes prior standalone_dockers/swarm_dockers rows so add() does not short-circuit on "Network already added". Each attempt uses a unique marker path so repeated runs never collide.
  • Outcome classification: ALTERNATE_TRIGGER_CONFIRMED (not a bypass). The variant reproduces on the vulnerable build and is blocked on the fixed build by the model mutator. The fix's primary mitigation (regex validation) does not cover server.destinations.add(), so a complete fix should extend validation to that component.
  • Scope/threat model: Coolify's SECURITY.md actively supports 4.x for security updates and does not exclude authenticated command injection from scope; the maintainers explicitly fixed this class in PR #9228. The variant stays within the same authenticated→root trust boundary as the parent CVE.
  • Other candidates considered and ruled out:
    • Destination\Show::submit() (update) — covered by the fix's regex validation on $network + mutator.
    • Destination\Show::delete() — covered by escapeshellarg().
    • Destination\Show $serverIp field — the server IP is escaped via SshMultiplexingHelper::escapedUserAtHost() (escapeshellarg) for the SSH here-doc sink, so it is not a clean variant of the network-field bug for the primary sink.
    • Server::created / InstallDocker destination creation — use hardcoded network names ('coolify'/'coolify-overlay'), not user-controlled.
  • Repo state: only read-only git show/git log/git rev-parse was used on the project-cache repo; HEAD remains at 575b0766d (beta.470). No checkout was performed.

CVE-2026-34594 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:39
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-34594 · REPRO-20
0:01
0:03
web search
0:05
web search
0:07
0:08
web search
0:09
web search
0:11
0:12
0:15
0:17
web search
0:18
web search
0:29
0:29
extract_facts
no facts extracted
0:30
0:30
0:30
supportrepro
0:31
0:31
0:31
0:32
0:32
0:32
0:35
0:35
0:36
0:36
0:39

Artifacts and Evidence for CVE-2026-34594

Scripts, logs, diffs, and output captured during the reproduction.

bundle/artifact_promotion_manifest.json13.6 KB
bundle/vuln_variant/source_identity.json1.7 KB
bundle/vuln_variant/root_cause_equivalence.json2.3 KB
bundle/repro/reproduction_steps.sh20.7 KB
bundle/repro/rca_report.md10.6 KB
bundle/repro/validation_verdict.json1.0 KB
bundle/repro/runtime_manifest.json1.2 KB
bundle/logs/vulnerable_marker_1.txt0.1 KB
bundle/logs/vulnerable_attempt_1.log0.3 KB
bundle/logs/fixed_attempt_1.log0.4 KB
bundle/logs/reproduction_steps.log6.7 KB
bundle/logs/stack_vulnerable.log1.0 KB
bundle/logs/setup_vulnerable.log2.3 KB
bundle/logs/vulnerable_attempt_2.log0.3 KB
bundle/logs/vulnerable_marker_2.txt0.1 KB
bundle/logs/stack_fixed.log1.0 KB
bundle/logs/setup_fixed.log0.4 KB
bundle/logs/fixed_attempt_2.log0.4 KB
bundle/vuln_variant/reproduction_steps.sh23.3 KB
bundle/vuln_variant/rca_report.md15.3 KB
bundle/vuln_variant/variant_manifest.json4.5 KB
bundle/vuln_variant/validation_verdict.json3.3 KB
bundle/vuln_variant/patch_analysis.md10.3 KB
bundle/vuln_variant/runtime_manifest.json1.6 KB
bundle/logs/vuln_variant/debug_fixed_evidence.txt0.7 KB
bundle/logs/vuln_variant/vulnerable_marker_1.txt0.1 KB
bundle/logs/vuln_variant/reproduction_steps.log4.2 KB
bundle/logs/vuln_variant/stack_vulnerable.log1.0 KB
bundle/logs/vuln_variant/setup_vulnerable.log0.6 KB
bundle/logs/vuln_variant/vulnerable_attempt_1.log0.7 KB
bundle/logs/vuln_variant/vulnerable_attempt_2.log0.7 KB
bundle/logs/vuln_variant/vulnerable_marker_2.txt0.1 KB
bundle/logs/vuln_variant/stack_fixed.log1.0 KB
bundle/logs/vuln_variant/setup_fixed.log0.4 KB
bundle/logs/vuln_variant/fixed_attempt_1.log0.5 KB
bundle/logs/vuln_variant/fixed_attempt_2.log0.5 KB
08 · How to Fix

How to Fix CVE-2026-34594

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-34594 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-34594

When does the CVE-2026-34594 command injection trigger?

It triggers at the moment the destination is created, via the StandaloneDocker::created boot event, and again when the destination is deleted, since every shell consumer of ->destination->network (ApplicationDeploymentJob, DatabaseBackupJob, StartService, the Init command, and proxy helpers) re-interpolates the unsanitized value.

Does exploiting CVE-2026-34594 require authentication?

Yes. It requires an authenticated Coolify user who already has destination-management permissions; it is not an unauthenticated remote code execution.

How severe is CVE-2026-34594?

It is rated high severity: an already-authorized but lower-trust user can escalate to root command execution on any server Coolify manages as a destination.

How can I reproduce CVE-2026-34594?

Download the verified script from this page and run it in an isolated environment against a Coolify instance before 4.0.0-beta.471. Create a Docker Destination whose network field contains shell metacharacters (e.g. x;id > /tmp/pwned #), and confirm the injected command executes as root on the destination server when the destination is created.
11 · References

References for CVE-2026-34594

Authoritative sources for CVE-2026-34594 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.