# Patch Analysis for CVE-2026-34047 Variant Review

## Target and threat model scope

The target is `coollabsio/coolify`, a web application that manages servers and applications. The repository security policy (`SECURITY.md`) says 4.x is supported for active security updates and asks reporters to disclose vulnerabilities affecting confidentiality, integrity, or availability. It does not exclude authenticated authorization bypasses or terminal/SSH command execution issues from scope.

For this issue, the relevant trust boundary is an authenticated but low-privileged team `member` crossing from browser/API input into server-side terminal and SSH functionality that is intended for team administrators/owners only. Local self-use or direct access to a host is not the claimed issue; the risk is misuse of Coolify's HTTP/WebSocket/API authorization boundary.

## What the fix changes

The patch between `v4.0.0-beta.470` (`575b0766d12bad2a78febff72ab59c017772bcf7`) and `v4.0.0-beta.471` (`914d7e0b50505bc1fd56c34974fca09ad354e92a`) changes `routes/web.php` so the terminal bootstrap endpoints enforce the same terminal authorization middleware as the visible terminal pages:

- `POST /terminal/auth` changes from `->name('terminal.auth')` to `->name('terminal.auth')->middleware('can.access.terminal')`.
- `POST /terminal/auth/ips` changes from `->name('terminal.auth.ips')` to `->name('terminal.auth.ips')->middleware('can.access.terminal')`.

The middleware alias is `can.access.terminal` (`app/Http/Kernel.php`) and it maps to `App\Http\Middleware\CanAccessTerminal`. That middleware requires authentication and then checks `auth()->user()->can('canAccessTerminal')`. The gate is defined in `app/Providers/AuthServiceProvider.php` as:

```php
Gate::define('canAccessTerminal', function ($user) {
    return $user->isAdmin() || $user->isOwner();
});
```

The WebSocket backend in `docker/coolify-realtime/terminal-server.js` was not changed by the terminal authorization fix. It still verifies a client by posting to `http://coolify:8080/terminal/auth`, retrieves allowed terminal hosts from `http://coolify:8080/terminal/auth/ips`, validates the requested SSH target against that list, and then reaches `pty.spawn('ssh', ...)`.

## Fix assumptions

The fix assumes all browser terminal/WebSocket sessions depend on those two Laravel bootstrap routes before command execution. Source inspection supports that assumption for the disclosed terminal sink:

- `terminal-server.js` has only one WebSocket server path, `/terminal/ws`.
- WebSocket handshake authorization calls only `/terminal/auth`.
- Per-connection host authorization calls only `/terminal/auth/ips`.
- The command execution sink (`pty.spawn('ssh', ...)`) is reached only after the connection stores `authorizedIPs` from `/terminal/auth/ips` and `isAuthorizedTargetHost()` accepts the SSH target.

The fix also assumes `canAccessTerminal` is the correct role predicate. It maps to owner/admin only, which matches the intended terminal access boundary described by the ticket and the existing UI route protection.

## Paths and inputs reviewed for missed coverage

1. **Direct WebSocket terminal path.** Reviewed `docker/coolify-realtime/terminal-server.js`. No alternate WebSocket path or second command-execution handler was found. Message handlers (`command`, `message`, `resize`, `pause`, `resume`, `ping`, `checkActive`) all operate within the same authenticated WebSocket session. Only `command` starts a PTY and it requires the `authorizedIPs` list fetched from `/terminal/auth/ips`.

2. **Terminal UI and resource terminal pages.** Reviewed `routes/web.php`. The global terminal page and application/database/service/server terminal pages already use `can.access.terminal`. The vulnerable gap was specifically the backend bootstrap endpoints. After the patch, those endpoints also carry the middleware.

3. **Ticket-suggested API-token/private-key chain.** The ticket describes a member generating an API token and retrieving SSH private keys. This is a different data path from terminal WebSocket bootstrap, so it was tested as a candidate variant. The runtime-backed probe creates a low-privileged Member token with `['read','write']` and exercises the `SecurityController` sensitive-data serialization logic. On both the vulnerable terminal release and the fixed release, the generated token does not have `read:sensitive` or `root`, so `private_key` is hidden in the private-key response. This path did not reproduce a private-key disclosure bypass.

4. **Latest release sanity check.** Source inspection of `v4.1.2` shows `/terminal/auth` and `/terminal/auth/ips` still include `can.access.terminal`, so the terminal bootstrap hardening persists beyond the patched version.

## Behavior before and after the fix

- **Before (`v4.0.0-beta.470`):** A low-privileged Member can call `/terminal/auth` and `/terminal/auth/ips` directly. The WebSocket server trusts those responses and can reach `pty.spawn('ssh', ...)` for a terminal-enabled host.
- **After (`v4.0.0-beta.471`):** The same Member receives `403` from both terminal bootstrap routes. The WebSocket server fails closed before fetching usable authorization data or starting the SSH PTY.
- **Candidate API-token/private-key path:** On both versions, a Member-created read/write token did not expose the private key material because the sensitive field is only serialized for `read:sensitive` or `root` token abilities.

## Completeness assessment

For the disclosed terminal WebSocket authorization bypass, the patch appears complete: the only backend terminal bootstrap endpoints trusted by the WebSocket command-execution sink now enforce the owner/admin terminal gate. I did not find a distinct fixed-version bypass to `pty.spawn('ssh', ...)` from another terminal entry point.

The separate API-token/private-key chain described in the ticket was tested as a candidate variant and did not validate under the current code path. It should remain covered by tests because future changes to token permissions or sensitive-data serialization could change this conclusion, but it is not a bypass of the terminal middleware fix based on the evidence gathered here.
