Skip to main content

Coolify Fixed 11 Critical CVEs in January. The Same Root Cause Struck Again in July

9 min readDora NodaDora Noda
Share
On this page

On January 8, 2026, security researchers disclosed 11 vulnerabilities in Coolify, the open-source, self-hosted PaaS alternative to Vercel, Heroku, and Railway. Every one of them scored between CVSS 9.4 and 10.0. At disclosure time, roughly 52,890 Coolify instances were sitting reachable on the public internet. Six months later, on July 7, Coolify shipped a fix for CVE-2026-34158 — a command-injection bug that let an authenticated user escape Docker confinement and run arbitrary commands on the host. Its CVSS score was 8.8, technically "High," not "Critical."

The score dropped. The bug didn't change. It's the same exploitation mechanics that produced most of January's 11 flaws, landing in the same product, six months after the last pileup was supposedly cleaned up. That recurrence — not the score — is the thing worth understanding, because it isn't really a story about Coolify's code quality. It's a story about what a single daemon running as root can and can't be made safe from, and what a Kubernetes-native control plane changes about that math.


The January Pileup: All 11, By CVE ID

Coolify wraps Docker Compose and Docker Swarm behind a web dashboard. You install it on one server, it takes root, it holds the Docker socket, and it SSHes into every other server you add using a key with root-equivalent access. That's the whole product: a single management daemon that can reach into any machine in your fleet and execute whatever the dashboard tells it to.

The January disclosure showed exactly what happens when that daemon's input isn't sanitized. Here's the full list:

CVECVSSFlaw
CVE-2025-6620910.0Command injection in database backup
CVE-2025-6621010.0Command injection in database import
CVE-2025-6621110.0Command injection in PostgreSQL init-script handling
CVE-2025-6621210.0Command injection in dynamic proxy configuration
CVE-2025-6621310.0Command injection in file-storage directory mounts
CVE-2025-5915710.0Command injection via the Git repository field
CVE-2025-6442010.0Root user's private SSH key disclosed to low-privileged users
CVE-2025-644199.7Command injection via docker-compose.yaml
CVE-2025-644249.4Command injection in Git source input fields
CVE-2025-591569.4Command injection via Docker Compose directives
CVE-2025-591589.4Stored XSS at project creation, targeting admins

Eleven CVEs, one pattern: user-controlled strings — a backup filename, a compose directive, a Git URL, a proxy label — get concatenated into a shell command that runs with root privileges on the host or on every remote server the daemon manages. Coolify's own advisories describe it as the same root cause "implementing the 'User Input → Shell' pattern without sufficient validation" in at least five independent subsystems. This wasn't one bad function. It was one bad architectural habit, expressed everywhere the product touches user input.

July 7: Same Pattern, Lower Score

Coolify patched all eleven by version 4.0.0-beta.451 and, by public accounts, no active exploitation was observed. Then on July 7, GitHub advisory GHSA-6h8g-wpxp-cq98 disclosed CVE-2026-34158: prior to version 4.0.0-beta.469, Coolify's executeInDocker() helper wraps user-controlled build commands in single quotes without escaping embedded single quotes inside them. An attacker who can edit an application's docker_compose_custom_build_command or docker_compose_custom_start_command settings can inject a stray ' to break out of the quoted string and run arbitrary commands on the host during the next deployment — the exact same "unsanitized string reaches a root-privileged shell" mechanics as CVE-2025-64419 and CVE-2025-59156 six months earlier.

Its CVSS is 8.8, not 9.4+, and that's worth being precise about rather than rounding up for effect: the score is lower mainly because exploitation requires an authenticated user with application-settings write access, not a pre-auth network position the way some of January's bugs did. That's a real difference in exploitability. It is not a difference in what happens once the bug fires — full host command execution, same as January — and it is not evidence the underlying problem got fixed. It's evidence the same architectural habit produced a twelfth instance of the same bug class inside two quarters.

Why the Pattern Keeps Repeating: One Daemon, One Root, No Scoping Layer

Here's the structural reason this isn't bad luck. In Coolify's architecture, there is no permission boundary between "an authenticated user editing an application's settings" and "arbitrary command execution as root on the host." The daemon that renders your dashboard is the same daemon that holds the Docker socket and the same daemon that SSHes into your fleet as root. When a bug lets user input reach a shell string anywhere in that daemon — a backup path, a compose field, a proxy label, a build command — there's no second gate to catch it. The vulnerability is the privilege escalation, because there was never a lower privilege level to escalate from.

That's why the fix for each of the twelve bugs has been "sanitize this one input" rather than "the scoping layer contained the blast radius." There's no scoping layer to contain anything. Every one of Coolify's advisories independently ends at the same place — root on the host — because every code path in a single, root-privileged daemon terminates at the same place. Patch the twelfth leak and you've patched the twelfth leak; you haven't touched the property that makes leak thirteen exist.

The Counter-Example: Kubernetes Also Gets Critical CVEs

This isn't an argument that Kubernetes-native control planes are immune to bugs — a fair reader should reject that claim on sight, and the record doesn't support it. In March 2025, "IngressNightmare" (CVE-2025-1974, CVSS 9.8) let an unauthenticated attacker on the pod network achieve remote code execution inside the ingress-nginx admission controller. Because that controller's service account typically has cluster-wide access to Secrets, the practical result in a default configuration was full cluster takeover — arguably as severe as anything in Coolify's January pileup.

The difference is what the fix looked like. IngressNightmare existed because one component's trust boundary was misconfigured: an admission webhook reachable over the network without authentication, bound to an over-privileged service account. The remediation — beyond patching the code — was to tighten the RBAC binding on that service account and restrict network reachability to the webhook with a NetworkPolicy. Those levers exist because Kubernetes' control plane is built around a scoping layer: RBAC governs what a given identity can touch, admission control can reject a request before it's applied, and a compromised component's blast radius is bounded by the permissions it was actually granted, not by what the machine's root user can do.

Coolify's root-daemon model has no equivalent lever. You cannot "reduce the daemon's privileges" without breaking the product, because remote root execution on every server in your fleet is the product. A Kubernetes-native control plane can, in principle, be misconfigured into looking like Coolify — an over-privileged service account is a real and common mistake. But the failure mode is a configuration to fix, not an architecture to work around. Coolify's twelve advisories in six months are what "no scoping layer to tighten" looks like when you total up the patches.

The Risk Calculus Checklist

None of this is specific to Coolify as a brand — the same four questions apply to any self-hosted PaaS or infrastructure daemon you're evaluating, including ones not mentioned here:

  1. Does a single process hold root, or root-equivalent access, across your whole fleet? If yes, every bug in that process is a fleet-compromise bug by default — there's no lower blast radius to fall into.
  2. Is there an RBAC or admission-control layer between an authenticated app-level action and host-level execution? If a bug is found in one resource type, does the fix stop at "patch this code path," or does a scoping layer independently limit what that code path could ever have reached?
  3. When a vendor's advisory says "fixed," does it describe closing one input, or tightening a boundary that would have caught the next input too? Twelve advisories in six months that each end at "root on the host" is a pattern to weigh, independent of any single CVSS score.
  4. How many instances are actually reachable on the public internet right now? A large exposed install base (Coolify's ~52,890 in January) turns a patch-lag window into a mass-exploitation window; check what your vendor's own security page or a service like Censys/Shodan shows for the product you're evaluating.

What This Means Going Forward

Coolify's maintainers have been transparent and fast with fixes — there's no indication of bad faith here, and no evidence of in-the-wild exploitation of either the January batch or CVE-2026-34158. That's exactly why this case is useful: the vendor is doing the patching part right, and the bugs are still recurring, because patching doesn't change the architecture that keeps producing the same bug class. If your platform's control plane is a single daemon with root over your fleet, expect this pattern to repeat again — not because the maintainers are careless, but because there's no scoping layer left to fail once the daemon itself is compromised.

A control plane built as a Kubernetes-native API server — the model Cluster API-based platforms use — doesn't eliminate this risk category, but it does give you something to point to when a bug lands: the RBAC role that bounded the blast radius, the admission policy that rejected the malformed request, the namespace the compromised component never had access to. That's the lever a single root daemon doesn't have.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Cluster-API-based control plane where RBAC and admission control scope what any given identity, human or agent, can actually touch. 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