← Back to Feed CACHED · 2026-06-30 15:18:33 · CACHE_KEY CVE-2026-34592
CVE-2026-34592 · CWE-639 · Disclosed 2026-06-29

Coolify cross-team IDOR — server and project lookups not scoped to current team

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A self-hosted Heroku clone forgot to check whose tenant you're in before handing over the server list

Coolify is an open-source PaaS that manages servers, applications, databases, and deployments — think Heroku/Vercel you run yourself. In versions prior to 4.0.0-beta.471, the server and project lookup endpoints fail to scope queries to the requesting user's team_id. Any authenticated user with a valid session can substitute another team's server ID or project ID into the URL and read that team's resources back. CWE-639 — classic Authorization Bypass Through User-Controlled Key (IDOR). CVSS vector AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N reflects confidentiality-only impact but with scope change, because the data crossing the boundary belongs to a different security principal (another team).

Vendor severity HIGH (7.7) is fair *for the multi-tenant deployments that are actually affected*. The catch: most Coolify installs in the wild are single-team (a solo dev or small shop running their own apps), so the population at risk is the subset of Coolify operators who invite users from outside their trust boundary — agencies hosting client environments, internal platform teams hosting multiple product squads, etc. For those operators, the leak isn't theoretical: project objects in Coolify carry environment variables, repo URLs, and references to server connection profiles that hold SSH credentials. That's why we keep this at HIGH despite the auth requirement.

"Cross-tenant IDOR on a PaaS that brokers SSH keys and deploy secrets — auth-gated but the blast radius is every other team's project metadata."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Obtain any authenticated session on the target Coolify instance

Attacker registers (if registration is open) or is invited as a low-privileged member of any team on the target Coolify instance. PR:L in the CVSS vector means a session cookie / API token is all that's required — no admin role, no team-owner role.
Conditions required:
  • Coolify instance reachable on the network the attacker is on
  • Open registration OR existing low-priv account in any team
Where this breaks in practice:
  • Most production Coolify instances are behind VPN or basic auth proxy
  • Self-hosted PaaS rarely has open registration — would-be tenant must be invited
Detection/coverage: Authentication logs in Coolify's PostgreSQL store; reverse-proxy access logs if Caddy/Traefik fronts it.
STEP 02

Enumerate other teams' resource IDs

Coolify uses incrementing or UUID-style identifiers for servers (/servers/{id}) and projects (/projects/{uuid}). Attacker scripts a sequential walk (for integer IDs) or harvests UUIDs from prior responses, leaked references, or shared CI runs. Standard curl + jq loop; no custom tooling.
Conditions required:
  • Knowledge of the API surface (documented at coolify.io/docs)
  • ID format is enumerable or guessable
Where this breaks in practice:
  • UUIDs reduce blind enumeration to oracle-style attacks
  • Rate limiting at the reverse proxy slows mass walks
Detection/coverage: Spike in 200-OK responses to /servers/* and /projects/* from a single session is the telltale; default Coolify telemetry does NOT alert on this.
STEP 03

Read cross-team server and project objects via direct ID reference

The vulnerable lookup paths execute Server::find($id) / Project::find($uuid) without the whereHas('team', currentTeam()) scope. Authenticated request to another team's resource returns 200 with full object — including server hostname, IP, connection settings, project name, repo URL, and references to associated environment variables and deployments.
Conditions required:
  • Target IDs from step 2
  • Bug present (i.e., target < 4.0.0-beta.471)
Where this breaks in practice:
  • The advisory specifies *server and project lookups* — secret/env-var endpoints may be separately scoped. Confirm what your version actually leaks before treating as 'full env-var exposure'.
Detection/coverage: Application-layer logging would show user_id from team A accessing resources owned by team B. Coolify does not log this by default; build it in front of the Nginx/Caddy proxy.
STEP 04

Pivot from leaked metadata to deployment compromise

With another team's server hostname, repo URL, and project structure, attacker can target the upstream Git repository (steal credentials, submit malicious PRs), brute-force or social-engineer SSH access to the managed server, or correlate exposed deployment data with other intel for a follow-on attack. This step is OUT of the CVE itself but is the realistic blast radius on a multi-tenant agency or platform-team install.
Conditions required:
  • Useful intel returned in step 3
  • Reachable target server or upstream repo
Where this breaks in practice:
  • MFA on Git provider
  • SSH key-based auth on managed servers (default Coolify config) prevents direct hostname-only pivot
Detection/coverage: GitHub/GitLab audit logs for unusual repo enumeration; SSH auth-failure logs on managed servers.
03 · Compensating Control

1
Upgrade to Coolify 4.0.0-beta.471 or later — The only complete fix. Pull the new coollabsio/coolify image, run the documented upgrade flow. Per noisgate remediation SLA, complete within 180 days; for multi-tenant installs treat as ≤ 30 days.
2
Audit team membership and revoke unused accounts — The bug requires *any* authenticated session. Cull dormant invitees and former contractors today. This is the cheapest compensating control and within the noisgate mitigation SLA of 30 days for HIGH.
3
Restrict Coolify dashboard to VPN / SSO-gated reverse proxy — If your instance is reachable on the public internet, front it with Cloudflare Access, Tailscale, or an SSO-aware reverse proxy. Deploy within 30 days to satisfy the mitigation SLA.
4
Enable application-layer audit logging for /servers/* and /projects/* — Use the reverse proxy to log requested IDs alongside the session's user_id. Run a daily diff: any access where the project's owning team ≠ the requester's team is the exploit signature. Stand up within the 30-day window.
5
Rotate SSH keys and env-var secrets stored in Coolify if multi-tenant — Assume leaked. Rotate before the next billing cycle. This is a remediation step, not a control — but a multi-tenant install that defers rotation is gambling on no prior exploitation having happened.
What doesn't work
  • WAFs / generic IDS rules: the request is structurally identical to a legitimate one — same path, same method, same headers, valid session cookie. Only the resource ID differs. No signature catches this.
  • MFA: doesn't help — the attacker IS authenticated. MFA gates session creation, not authorization scope inside the app.
  • Network segmentation alone: if all teams reach the same Coolify URL, segmentation doesn't prevent the IDOR. Per-team Coolify instances would, but that's a re-architecture, not a control.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone reported as of 2026-06-30. No GreyNoise tags, no campaigns, no incident reports.
Public PoCGitHub Security Advisory GHSA (coollabsio/coolify) describes the flaw; no weaponized PoC repo observed but the bug is trivially reproducible with curl once you have any session.
EPSSNot yet scored (CVE published 2026-06-29). Expect <1% within 30 days given auth requirement and lack of mass exposure.
KEVNot listed. Unlikely to be added — confidentiality-only, auth-gated, narrow population.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N → 7.7 HIGH. Scope change reflects cross-tenant boundary. No I or A impact in the CVE itself.
Affected versionsAll Coolify releases prior to 4.0.0-beta.471.
Fixed version4.0.0-beta.471 and later. No distro backports — Coolify ships as Docker images via coollabsio/coolify.
Exposure dataShodan/Censys queries for coolify favicon hash typically return ~2,000–5,000 public Coolify dashboards globally. Of these, the subset that are multi-team is the real attack surface — likely a small minority.
Disclosed2026-06-29 via coollabsio GitHub Security Advisory.
ReporterCoordinated disclosure via Coolify maintainers (coollabsio). Researcher attribution per the GHSA advisory.

Sources.

  1. Coolify GHSA — coollabsio/coolify Security Advisories
  2. Coolify project — coollabsio/coolify GitHub
  3. CWE-639: Authorization Bypass Through User-Controlled Key
  4. The Hacker News — Coolify Discloses Critical Flaws
  5. heise online — Coolify critical vulnerabilities
  6. wz-it — Coolify CVE Overview 2025/2026
  7. Coolify documentation
  8. FIRST CVSS 3.1 specification
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.0/10)

Why this verdict

  • Auth-gated (PR:L): the attacker must already hold a valid session on the target instance. That eliminates the drive-by-internet population.
  • Single-tenant installs are unaffected in practice: if your Coolify only has one team, there is no other team to enumerate. The bulk of self-hosted Coolify deployments fall here.
  • Role multiplier — orchestration platform: Coolify is a deployment broker holding SSH connection profiles, env vars, and Git creds. Cross-tenant read on Server and Project objects can hand an attacker enough metadata to pivot to managed hosts. This is the reason we hold at HIGH instead of dropping to MEDIUM.
  • No I or A impact in the CVE itself: the chain ends at *read* of another team's resources. Modification, deletion, deploy-time RCE, and direct credential theft would require a chained second bug or are scope-limited to what the lookup returns.
  • No KEV, no PoC weaponization, no campaign telemetry: purely a hygiene patch on the timeline the SLA dictates.

Why not higher?

Not CRITICAL because PR:L (authentication required), no integrity or availability impact, and the population that is actually multi-tenant is a minority of Coolify deployments. CRITICAL would require unauthenticated reachability or direct RCE/credential-theft impact on the typical install.

Why not lower?

Not MEDIUM because Coolify is canonically a high-value-role component (deployment orchestration with secret material). On the subset of installs that ARE multi-tenant (agencies, internal platform teams), the chain credibly extends from metadata read to managed-server pivot. Generic CWE-639 on a CRUD app would be MEDIUM; on a PaaS it earns the role multiplier.

06 · Verification

Crowdsourced verification payload.

Run this from an auditor workstation with shell access to the Coolify host (or anywhere docker can reach the running container). Invoke as ./check_coolify.sh <coolify_container_name_or_id> — example: ./check_coolify.sh coolify. No special privileges beyond being in the docker group.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate verification: CVE-2026-34592 — Coolify cross-team IDOR
# Outputs: VULNERABLE / PATCHED / UNKNOWN
set -u

CONTAINER="${1:-coolify}"
FIXED="4.0.0-beta.471"

if ! command -v docker >/dev/null 2>&1; then
  echo "UNKNOWN — docker CLI not available"
  exit 2
fi

if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
  echo "UNKNOWN — container '$CONTAINER' not found"
  exit 2
fi

# Extract version from the running container. Coolify exposes /api/health and writes
# a versions.json; fall back to image tag.
VER="$(docker exec "$CONTAINER" sh -c 'cat /var/www/html/versions.json 2>/dev/null | grep -oE "\"coolify\"\s*:\s*\"[^\"]+\"" | head -1 | sed -E "s/.*:\s*\"([^\"]+)\".*/\\1/"' 2>/dev/null)"

if [ -z "$VER" ]; then
  VER="$(docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.version" }}' "$CONTAINER" 2>/dev/null)"
fi

if [ -z "$VER" ]; then
  VER="$(docker inspect --format '{{.Config.Image}}' "$CONTAINER" | awk -F: '{print $2}')"
fi

if [ -z "$VER" ]; then
  echo "UNKNOWN — could not determine Coolify version"
  exit 2
fi

# Lexicographic compare works for Coolify's semver-ish 4.0.0-beta.NNN scheme
# Normalize: strip leading 'v'
VER="${VER#v}"

verlte() { [ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }

if verlte "$FIXED" "$VER"; then
  echo "PATCHED — Coolify $VER (fixed in $FIXED)"
  exit 0
else
  echo "VULNERABLE — Coolify $VER < $FIXED — upgrade to $FIXED or later"
  exit 1
fi