Skip to content

Cross-team application domain enumeration via domains_by_server endpoint

Moderate
andrasbacsai published GHSA-9x6p-29p3-h466 Jun 25, 2026

Software

coollabsio/coolify

Affected versions

<= 4.0.0-beta.406

Patched versions

v4.0.0-beta.464

Description

Summary

GET /api/v1/servers/{server_uuid}/domains?uuid={app_uuid} bypasses team scoping when the optional uuid query parameter is provided. Any authenticated API user can enumerate domain names (FQDNs) of applications belonging to other teams.

This is a separate finding from GHSA-5p5w-h58c-2h5m (cross-team deployment info disclosure via deployment_by_uuid), affecting a different controller and different data.

Details

File: app/Http/Controllers/Api/ServersController.php, lines 285-296

public function domains_by_server(Request $request)
{
    $teamId = getTeamIdFromToken();
    if (is_null($teamId)) {
        return invalidTokenResponse();
    }
    $uuid = $request->get('uuid');
    if ($uuid) {
        // SHORT-CIRCUIT: No team check, queries globally
        $domains = Application::getDomainsByUuid($uuid);
        return response()->json(serializeApiResponse($domains));
    }
    // Only the fallback path (no ?uuid= param) is team-scoped:
    $projects = Project::where('team_id', $teamId)->get();
    ...
}

Application::getDomainsByUuid() in app/Models/Application.php, lines 1962-1971:

public static function getDomainsByUuid(string $uuid): array
{
    $application = self::where('uuid', $uuid)->first();  // Global query, no team filter
    if ($application) {
        return $application->fqdns;
    }
    return [];
}

When the ?uuid= query parameter is provided, the endpoint short-circuits to Application::getDomainsByUuid() which performs a global query without team scoping. The {server_uuid} path parameter is not even validated against the caller's team in this code path — the function returns before reaching the team-scoped fallback logic.

Correct Pattern (same controller, other endpoints)

All other ServersController methods correctly scope by team:

// servers(), server_by_uuid(), etc.
$server = ModelsServer::whereTeamId($teamId)->whereUuid($uuid)->first();

And the fallback path of domains_by_server (when no ?uuid= param) correctly scopes:

$projects = Project::where('team_id', $teamId)->get();

Impact

Any authenticated API user can discover the configured domain names (FQDNs) of applications belonging to other teams. This leaks:

  • Internal/private domain names (e.g., internal-tool.company.com)
  • Application URL structure and naming conventions
  • Which applications exist and their deployment domains

The {server_uuid} path parameter can be any valid server UUID (even the attacker's own), since it's not used when ?uuid= is provided.

Suggested Fix

Add team scoping to the ?uuid= code path:

if ($uuid) {
    $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $uuid)->first();
    if ($application) {
        return response()->json(serializeApiResponse($application->fqdns));
    }
    return response()->json(serializeApiResponse([]));
}

Or scope getDomainsByUuid to accept a teamId parameter.

Severity

Moderate

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
Low
Integrity
None
Availability
None

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:L/I:N/A:N

CVE ID

CVE-2026-27956

Weaknesses

Authorization Bypass Through User-Controlled Key

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. Learn more on MITRE.