Skip to main content

Coolify's Second Critical RCE of 2026: How a Dockerfile Path Became a Shell (CVE-2026-34038)

11 min readDora NodaDora Noda
Share
On this page

Six months after disclosing 11 critical flaws at once, Coolify disclosed another CVSS 9.9. This time the trigger was not a sophisticated exploit chain. It was a text field on the deploy screen: tell Coolify where your Dockerfile lives, and if that path contains shell commands, they run — with host-level privileges, during a routine deployment, by any user with write access to a single application.

The facts, up front

CVE-2026-34038 is an authenticated OS command injection vulnerability (CWE-78) in Coolify's application deployment handling, disclosed in early July 2026 and carried in CISA's vulnerability bulletin for the week of July 6. The headline numbers:

  • Severity: CVSS 9.9, critical.
  • Who can exploit it: any authenticated user with application "write" permissions. No admin role, no SSH access, no infrastructure privileges required.
  • How: shell metacharacters in deploy-time fields such as dockerfile_location and deployment commands are concatenated into shell commands executed during a normal deploy.
  • Payoff: remote code execution plus exfiltration of sensitive environment variables — database credentials, API keys, secrets — through deployment logs.
  • Fix: upgrade to 4.0.0-beta.469 or later.

One detail from the published proof of concept deserves emphasis: the exfiltration works even when the build environment isolates the Docker socket. The vulnerable layer sits above container isolation, in the deployment pipeline itself, so hardening the runtime boundary underneath does not stop it.

If you run Coolify, the immediate action is a patch, a secret rotation, and a log review — the checklist is at the end of this post. But the more durable story is what this flaw shares with everything Coolify disclosed in January, and what that pattern says about the security architecture of a single-daemon PaaS.

How a deploy field becomes a shell

The attack path has four steps, and none of them looks like hacking:

  1. Set a poisoned value through the normal UI. The attacker edits an application they legitimately have write access to and puts shell commands inside dockerfile_location (or a deployment command field). This is a routine configuration action, the kind every deploy pipeline performs.
  2. Trigger an ordinary deploy. No exploit harness, no crafted packet — just the deploy button. During the build/deploy phase, Coolify concatenates the attacker-controlled value into a shell command string.
  3. Commands execute with host-level privileges. The injected commands break out of the intended build context and run on the deployment host, not merely inside a throwaway build container.
  4. Secrets leave through deployment logs. The command output — including environment variables from the instance's secret store — lands in deployment logs the attacker can read from the same UI. No separate exfiltration channel is needed; the platform's own observability surface carries the loot out.

Step 4 is the part that converts a code-execution bug into an instance-wide compromise. Deployment logs are a legitimate, routinely-viewed surface. When the same privileged process that executes deploys also holds every application's secrets and renders every application's logs, one injection point collapses three security boundaries at once: execution, secret storage, and observability.

January's 11 flaws, six months later

To understand why a single new CVE matters, rewind to January 2026. Security researchers disclosed 11 critical vulnerabilities in Coolify in one batch, with severity scores reaching the maximum 10.0. The Hacker News and SC Media coverage from the week of January 9 put over 52,000 exposed instances at risk — concentrated in Germany and the United States — and Censys published an emergency advisory on the cluster. Two entries from that batch show the range:

January 2026 batchJuly 2026 (CVE-2026-34038)
Count11 critical flaws at onceSingle flaw (plus a sibling, below)
Top severityCVSS 10.0 (CVE-2025-64420: low-privileged users could read the root user's private key and SSH in as root)CVSS 9.9
Privilege requiredAs low as unauthenticated / low-privilegedAuthenticated with application write permission
Signature impactRCE as root via docker-compose.yaml injection (CVE-2025-64419, 9.7); auth bypass; SSH key theftRCE plus secret exfiltration via deployment logs
Exposure52,000+ internet-facing instancesAny unpatched instance with more than one user role

At the time, the charitable reading was that January represented a one-time event: a beta-era codebase (the flaws predated 4.0.0-beta.420.7) getting its first serious adversarial audit, with everything fixed in one sweep. Six months and dozens of subsequent beta releases should, on that theory, have closed the vulnerability classes, not just the instances.

CVE-2026-34038 is the evidence against that theory. It is not a leftover from the January batch. It is a new instance of the same class — untrusted deploy input reaching a shell — in a different field, disclosed half a year later, scoring 9.9. And it did not arrive alone: CVE-2026-34597, an authenticated host RCE via the user-supplied install_command being concatenated directly into a shell command on the deployment host (CVSS 8.8, fixed in 4.0.0-beta.470), was disclosed in nearly the same window. Two deploy-field-to-shell flaws, two adjacent patch releases, six months after the "everything is fixed" sweep.

The same bug class, six different fields

The recurrence is the story, so here is the receipts list. Every entry below is OS command injection (or direct RCE) through a deploy-adjacent input, each fixed by adding validation to that specific field:

  • docker-compose.yaml contents (January 2026, CVE-2025-64419, CVSS 9.7): a malicious compose service mounting the host filesystem yielded root-level command execution, bypassing container isolation entirely.
  • Git repository input during project creation (January 2026, CVE-2025-59157): command injection through the repository field, fixed in 4.0.0-beta.420.7.
  • Docker Compose directives during project setup (January 2026, CVE-2025-59156): a low-privileged member injecting arbitrary compose directives to reach root-level execution.
  • PostgreSQL init script filename (fixed in 4.0.0-beta.451, CVE-2025-66211): authenticated command injection through a database provisioning field most operators would never think of as executable input.
  • Health-check commands and file-location paths (fixed in 4.0.0-beta.467): command injection in health checks plus missing path validation on file locations — patched just weeks before the July disclosures.
  • dockerfile_location and deployment commands (July 2026, CVE-2026-34038, CVSS 9.9) and install_command (CVE-2026-34597, CVSS 8.8, fixed in .470): the two newest instances.

Six fields, one shape: a string the operator typed into a config screen ends up inside a shell command. Each fix adds validation at the site of the reported field — path checks here, command filtering there. That is whack-a-mole, not structural closure. A structurally closed class would mean untrusted deploy inputs cannot reach a shell by construction: parameterized execution, no string-concatenated shell commands anywhere on the deploy path, secrets unreachable from the execution context. The July disclosures prove the codebase had not reached that state. Every new deploy-adjacent field remains a candidate for the seventh entry.

Why one daemon means one blast radius

None of the above is an argument that Coolify's maintainers are careless — pre-release software getting audited and patched is the system working. It is an argument about architecture: when (not if) a deploy-input bug exists, what does it cost? Here the single-daemon design sets the price.

Coolify runs as one privileged management plane on your server: it holds every application's environment variables, drives Docker through the host socket, executes deployments, and serves logs back through one UI. That consolidation is the product's appeal — one install, one panel, everything managed. It is also why CVE-2026-34038's payoff is instance-wide rather than app-scoped:

Single privileged daemon (Coolify-style)RBAC-scoped, multi-component control plane
Who holds tenant secretsOne process/store for every app on the instanceScoped per tenant or workload; the deploy executor never sees secrets outside its scope
Privilege of the deploy pathEffectively host-equivalent: drives the Docker socket, runs shell on the hostLeast-privilege service accounts; a bug in one component inherits only that component's narrow role
What "write to one app" can reachHost execution plus every secret, one injection awayThe blast radius of that component's RBAC grant — typically that app's namespace, not the fleet
Exfil channelBuilt in: deployment logs flow back through the same UI the attacker already usesLogs and secrets live behind separate authorization boundaries
Fix shape for this bug classPer-field validation patches, field by fieldDefense in depth: even an unpatched field hits component and RBAC boundaries before host execution

The point of the right-hand column is not that Kubernetes-style platforms never have CVEs — they do. It is that their worst case for this bug class is structurally smaller. When the deploy executor is a narrowly-scoped component rather than the same process that guards every secret, "write access to one app" and "read every secret on the instance" are separated by architecture, not by the hope that every input field got its validation patch. A Cluster-API-based platform inherits this shape from its substrate: machine lifecycle, workload scheduling, and secret storage are distinct controllers with distinct RBAC grants, so a single injection has to cross multiple authorization boundaries to become instance-wide.

Judge a self-hosted PaaS the way an attacker prices it: not by CVE count, but by what one bug in the deploy path can reach.

What to do Monday morning

If you operate Coolify, do the immediate items now and the structural ones this quarter.

Immediate — this week:

  1. Upgrade to 4.0.0-beta.470 or later. The .469 release fixes CVE-2026-34038; .470 additionally fixes the sibling install_command RCE (CVE-2026-34597). Stopping at .469 leaves a known host-RCE next door.
  2. Rotate every secret stored in the instance. Assume that any application with more than one writer may have had its environment variables read through deployment logs. Database credentials, API keys, webhook signing secrets — all of them, not just the "important" apps.
  3. Review deployment logs for cross-team reads. Look for deploys triggered by users outside the owning team, unusual dockerfile_location or command values, and log views of applications the viewer does not own. Functional exploit code for this class of flaw circulates quickly.
  4. Reduce who holds application write permission until the upgrade and rotation are done. The exploit needs only that role, so every holder is a potential entry point.

Structural — this quarter:

  1. Map where tenant secrets live relative to the deploy execution path. If one process both runs untrusted-input-adjacent shell commands and can read every secret, you have CVE-2026-34038's blast radius by design.
  2. Ask what privilege the deploy executor holds. Host-equivalent execution for a routine app deploy is the gap between "app compromise" and "server compromise." Narrow it or isolate it.
  3. Check the exfil story for your observability surfaces. Logs, build output, and error messages that flow back to low-privileged viewers are exfiltration channels the day an injection bug lands. Know who can read whose logs.

The recurrence is the signal

CVE-2026-34038 on its own is a patch-and-rotate Tuesday. What makes it worth a post is the company it keeps: six deploy-adjacent fields, the same injection shape, across seven months of releases — with the two newest scoring 9.9 and 8.8 half a year after the January sweep. The January batch could be read as a beta-era audit event. July reads as a property of the architecture: a single privileged daemon where untrusted deploy inputs, host-level execution, the secret store, and the log viewer all meet in one process.

Self-hosting a PaaS means inheriting its blast-radius design along with its convenience. The question to ask of any platform you install on your own machines is not "has it had CVEs" — everything has — but "when the next deploy-path bug lands, what does it cost?" Architectures that answer with a scope boundary will keep beating architectures that answer with a patch release.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with an RBAC-scoped control plane instead of a single privileged daemon. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex