## Summary

No distinct fixed-version bypass or materially different terminal trigger was validated for CVE-2026-34047. The original bug is that Coolify's terminal WebSocket backend trusted `/terminal/auth` and `/terminal/auth/ips`, while those bootstrap routes only checked authentication in `v4.0.0-beta.470`. The patch in `v4.0.0-beta.471` adds `can.access.terminal` to both bootstrap routes. Source review found that the WebSocket command-execution sink still depends on those routes, and runtime variant testing of the ticket-suggested API-token/private-key chain did not expose private key material to a low-privileged Member on either the vulnerable or fixed target.

## Fix Coverage / Assumptions

The original fix relies on this invariant: every terminal WebSocket session that can reach `docker/coolify-realtime/terminal-server.js` command execution must first pass the Laravel terminal authorization bootstrap endpoints.

The fix explicitly covers:

- `POST /terminal/auth` in `routes/web.php`, now protected by `->middleware('can.access.terminal')`.
- `POST /terminal/auth/ips` in `routes/web.php`, now protected by `->middleware('can.access.terminal')`.
- The authorization predicate in `app/Http/Middleware/CanAccessTerminal.php`, which aborts non-admin/non-owner users with `403`.
- The `canAccessTerminal` gate in `app/Providers/AuthServiceProvider.php`, which returns true only for `isAdmin()` or `isOwner()`.

The reviewed command sink is `pty.spawn('ssh', ...)` in `docker/coolify-realtime/terminal-server.js`. That file exposes only `/terminal/ws`, validates the WebSocket handshake by posting to `/terminal/auth`, fetches host authorization from `/terminal/auth/ips`, and checks `isAuthorizedTargetHost()` before spawning SSH. I did not find a second WebSocket path or alternate message type that starts a PTY without those bootstrap routes.

The fix does not directly change unrelated API token or private-key API authorization. That path was therefore tested as a candidate because the ticket mentioned it as part of the broader exploit chain. The test showed no private key disclosure with a Member-created read/write token.

## Variant / Alternate Trigger

Tested candidate: low-privileged Member API-token/private-key path.

- Entrypoint: authenticated Member token creation semantics equivalent to the `/security/api-tokens` Livewire component (`app/Livewire/Security/ApiTokens.php`).
- API data path: `/api/v1/security/keys` route in `routes/api.php`, implemented by `App\Http\Controllers\Api\SecurityController::keys()`.
- Sensitive-data guard: `app/Http/Middleware/ApiSensitiveData.php`, which grants private-key serialization only when the token can `root` or `read:sensitive`.
- Runtime result: the generated Member token had abilities `['read','write']`, `TOKEN_CAN_READ_SENSITIVE=false`, and the serialized private-key response omitted the `private_key` field on both targets.

This candidate is not a bypass because it does not reach the same terminal command-execution sink and it did not disclose SSH private key material on the fixed target. It is best treated as a ruled-out adjacent chain rather than a confirmed variant.

Other paths reviewed:

- `/terminal/ws` WebSocket command message: still depends on `/terminal/auth` and `/terminal/auth/ips`.
- `message`, `resize`, `pause`, `resume`, `ping`, and `checkActive` WebSocket messages: do not start a new PTY without an active session or are non-command control messages.
- Global and resource terminal UI routes in `routes/web.php`: already use `can.access.terminal`.
- Latest tagged release `v4.1.2`: source inspection confirms `/terminal/auth` and `/terminal/auth/ips` still carry `can.access.terminal`.

## Impact

- Package/component affected: `coollabsio/coolify`, terminal authorization routes in `routes/web.php`, terminal WebSocket backend in `docker/coolify-realtime/terminal-server.js`, and the adjacent API-token/private-key path reviewed for variant coverage.
- Affected versions tested: vulnerable `v4.0.0-beta.470` at commit `575b0766d12bad2a78febff72ab59c017772bcf7`; fixed `v4.0.0-beta.471` at commit `914d7e0b50505bc1fd56c34974fca09ad354e92a`.
- Latest release checked by source inspection: `v4.1.2` at commit `e7dff30b7c998c301fd91bd169727b90c59ec291` retains the terminal middleware on the bootstrap endpoints.
- Risk level and consequences for the parent issue: critical authenticated authorization bypass to terminal-backed command execution on managed hosts.
- Risk level for this variant stage result: no new validated risk beyond the parent issue.

## Impact Parity

- Disclosed/claimed maximum impact for the parent: authenticated low-privileged Member can reach terminal WebSocket command execution on managed hosts and potentially chain API/private-key access.
- Reproduced impact from this variant run: no bypass. The Member API-token/private-key candidate was exercised on both tested versions, but private key material remained hidden and no terminal command execution path was reached through a distinct entry point.
- Parity: `none` for a new variant/bypass.
- Not demonstrated: a fixed-version terminal bypass, fixed-version managed-host command execution, or fixed-version SSH private-key disclosure to a Member.

## Root Cause

The parent root cause is insufficient server-side authorization on the terminal bootstrap routes. In `v4.0.0-beta.470`, `/terminal/auth` and `/terminal/auth/ips` only require an authenticated session. The WebSocket backend trusts those endpoints and uses their output to decide whether to spawn SSH.

The fixed version adds the missing authorization middleware to those exact bootstrap routes. Because the WebSocket sink still relies on those routes and no alternate unauthenticated/Member-authorized terminal bootstrap was found, the same underlying bug could not be reached from a distinct fixed-version path.

The ticket-suggested API-token/private-key chain uses different code. Runtime testing showed the relevant sensitive-data invariant holds for the tested Member token: without `read:sensitive` or `root`, `SecurityController` hides `private_key`. Therefore this adjacent chain did not demonstrate the same root cause reaching the same impact after the terminal fix.

Known fix commit/tag tested: `v4.0.0-beta.471` (`914d7e0b50505bc1fd56c34974fca09ad354e92a`).

## Reproduction Steps

1. Run `bundle/vuln_variant/reproduction_steps.sh` from the bundle root, or set `PRUVA_ROOT` to the bundle path and run it from any directory.
2. The script creates isolated Git worktrees for `v4.0.0-beta.470` and `v4.0.0-beta.471`, prepares a local PHP/Laravel SQLite runtime, creates an owner, a low-privileged Member, and a valid private key record, then creates a Member read/write token and exercises the private-key serialization path on both targets.
3. Expected evidence for the negative result:
   - `bundle/logs/vuln_variant/vulnerable_api_token_private_key_probe.log` shows `ROLE=member`, `TOKEN_ABILITIES=["read","write"]`, `TOKEN_CAN_READ_SENSITIVE=false`, and `LEAKED_PRIVATE_KEY_MARKER=false`.
   - `bundle/logs/vuln_variant/fixed_api_token_private_key_probe.log` shows the same values on the fixed target.
   - `bundle/logs/vuln_variant/reproduction_steps.log` summarizes `NO BYPASS`.

The script exits `1` by design when no fixed-version bypass is reproduced. It completed twice without crashing.

## Evidence

Primary evidence files:

- `bundle/vuln_variant/reproduction_steps.sh` — stage-specific runtime variant probe.
- `bundle/vuln_variant/runtime_manifest.json` — runtime manifest with tested target commits and proof artifact list.
- `bundle/logs/vuln_variant/reproduction_steps.log` — full orchestration log from the final run.
- `bundle/logs/vuln_variant/vulnerable_api_token_private_key_probe.log` — vulnerable-version candidate output.
- `bundle/logs/vuln_variant/fixed_api_token_private_key_probe.log` — fixed-version candidate output.
- `bundle/logs/vuln_variant/vulnerable_version.txt` — tested vulnerable commit.
- `bundle/logs/vuln_variant/fixed_version.txt` — tested fixed commit.

Key excerpts from the final run:

```text
[vulnerable] ROLE=member
[vulnerable] TOKEN_ABILITIES=["read","write"]
[vulnerable] TOKEN_CAN_READ_SENSITIVE=false
[vulnerable] LEAKED_PRIVATE_KEY_MARKER=false

[fixed] ROLE=member
[fixed] TOKEN_ABILITIES=["read","write"]
[fixed] TOKEN_CAN_READ_SENSITIVE=false
[fixed] LEAKED_PRIVATE_KEY_MARKER=false

Summary: vulnerable_leak=false fixed_leak=false vulnerable_sensitive=false fixed_sensitive=false
NO BYPASS: Member token path ran on both targets but did not expose private-key material on the fixed target
```

Source evidence:

- `routes/web.php`: `/terminal/auth` and `/terminal/auth/ips` gain `can.access.terminal` in `v4.0.0-beta.471`.
- `app/Http/Middleware/CanAccessTerminal.php`: non-admin/non-owner users are blocked.
- `docker/coolify-realtime/terminal-server.js`: the WebSocket terminal sink still depends on the two Laravel bootstrap endpoints.
- `app/Http/Middleware/ApiSensitiveData.php`: sensitive API output requires `root` or `read:sensitive` token ability.

## Recommendations / Next Steps

- Keep the `can.access.terminal` middleware on all terminal bootstrap routes, not only visible UI routes.
- Add regression tests for Member access to both `/terminal/auth` and `/terminal/auth/ips` returning `403`.
- Add an end-to-end WebSocket regression test asserting a Member session cannot reach `pty.spawn('ssh', ...)` after `/terminal/auth` or `/terminal/auth/ips` rejects the session.
- Add API regression tests ensuring Member-created `read` or `read/write` tokens cannot serialize `private_key` from `/api/v1/security/keys` unless a deliberately authorized `read:sensitive` or `root` ability is present and permitted by policy.
- Consider centralizing terminal authorization in a service used by both Laravel and the WebSocket backend, so future terminal bootstrap endpoints cannot omit the owner/admin gate.

## Additional Notes

- Idempotency confirmation: `bundle/vuln_variant/reproduction_steps.sh` was executed twice successfully. Both executions completed and produced the same negative verdict pattern. The command exits `1` because no fixed-version bypass was found, which is the required script convention for a negative variant result.
- The candidate runtime probe uses Laravel bootstrap/model/controller-equivalent logic with a SQLite database rather than starting the full HTTP server. This was sufficient for the tested API-token/private-key serialization path and avoided reusing the previous stage's terminal reproducer.
- The stage did not modify the repro checkout. It used isolated Git worktrees under `/tmp/coolify-vuln-variant/worktrees` for version testing.
