CVE-2026-34047: Verified Reproduction
CVE-2026-34047: Coolify terminal WebSocket endpoints lack proper authorization checks, allowing low-privileged members to access terminal functionality and achieve remote command execution on managed hosts.
CVE-2026-34047 is verified against coollabsio/coolify · Composer. Affected versions: <= 4.0.0-beta.470. Fixed in 4.0.0-beta.471. Vulnerability class: Auth Bypass. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00275.
What Is CVE-2026-34047?
CVE-2026-34047 is a critical authorization bypass (CWE-863) in Coolify where the backend terminal WebSocket bootstrap endpoints check only authentication, not authorization, letting a low-privileged team Member reach terminal functionality and execute commands as root on managed hosts. Pruva reproduced it (reproduction REPRO-2026-00275).
CVE-2026-34047 Severity & CVSS Score
CVE-2026-34047 is rated critical severity, with a CVSS base score of 9.9 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected coollabsio/coolify Versions
coollabsio/coolify · Composer versions <= 4.0.0-beta.470 are affected.
How to Reproduce CVE-2026-34047
pruva-verify REPRO-2026-00275 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00275/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-34047
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
Authenticated low-privileged Member session cookies and WebSocket terminal SSH command payload
- POST /terminal/auth/ips
- POST /terminal/auth
- WebSocket /terminal/ws command message
- terminal-server.js node-pty ssh
How the agent worked
Root Cause and Exploit Chain for CVE-2026-34047
CVE-2026-34047 is an authorization bypass in Coolify's terminal flow. The user-facing terminal pages are protected by the can.access.terminal / canAccessTerminal authorization gate, but the backend bootstrap endpoints used by the terminal WebSocket server (POST /terminal/auth and POST /terminal/auth/ips) only require authentication in the vulnerable release. A low-privileged authenticated team member can call those endpoints directly, obtain an authorized host list, connect to the real /terminal/ws WebSocket service, and cause Coolify's terminal server to spawn an SSH command toward a managed host.
- Package/component affected:
coollabsio/coolify, specifically the Laravel terminal authorization routes inroutes/web.phpand thedocker/coolify-realtime/terminal-server.jsWebSocket terminal backend. - Affected versions: Coolify versions up to and including
4.0.0-beta.470are affected according to the ticket. - Patched version:
4.0.0-beta.471/ the middleware fix addingcan.access.terminalto the terminal bootstrap routes. - Risk level and consequences: Critical. An authenticated low-privileged team member can bypass terminal authorization and reach the command-execution path intended only for team administrators/owners. In a real Coolify deployment, this can provide command execution on configured/managed hosts through Coolify's terminal SSH flow.
Impact Parity
- Disclosed/claimed maximum impact: Code execution / remote command execution on managed hosts by an authenticated low-privileged
Memberuser. - Reproduced impact from this run: Full WebSocket terminal command execution path was reproduced. The script authenticates as a Coolify team
member, receives HTTP 200 from/terminal/authand/terminal/auth/ips, opens/terminal/ws, sends a terminal SSH command payload, and receives output from a managed-host SSH peer after the host executes the attacker-supplied shell commands (printf 'REPRO_WS_COMMAND_EXECUTED\n'; whoami; hostname; pwd). - Parity:
fullfor the requested WebSocket terminal flow and code/command-execution impact. The managed host is a local unprivileged SSH peer created by the script, but the boundary crossed is the real Coolify terminal WebSocket server and OpenSSH command path. - Not demonstrated: The script does not retrieve a production SSH private key or spawn a root shell on a real external host. It proves the terminal authorization bypass reaches real command execution through the product WebSocket/SSH path using a controlled local managed-host peer.
Root Cause
In the vulnerable route definitions, Coolify protects the visible terminal pages with can.access.terminal but leaves the terminal backend authorization endpoints without that middleware:
Route::get('/terminal', TerminalIndex::class)->name('terminal')->middleware('can.access.terminal');
Route::post('/terminal/auth', function () {
if (auth()->check()) {
return response()->json(['authenticated' => true], 200);
}
return response()->json(['authenticated' => false], 401);
})->name('terminal.auth');
Route::post('/terminal/auth/ips', function () {
if (auth()->check()) {
$team = auth()->user()->currentTeam();
$ipAddresses = $team->servers
->where('settings.is_terminal_enabled', true)
->pluck('ip')
->filter()
->values();
return response()->json(['ipAddresses' => $ipAddresses->all()], 200);
}
return response()->json(['ipAddresses' => []], 401);
})->name('terminal.auth.ips');
The real terminal WebSocket server (docker/coolify-realtime/terminal-server.js) trusts those endpoints. During WebSocket verification it calls http://coolify:8080/terminal/auth; on connection it calls http://coolify:8080/terminal/auth/ips; when a client sends a command message it parses the SSH target, checks only whether the target is present in the returned authorized IP list, and then calls pty.spawn('ssh', ...). Because the backend route authorization is too weak, a low-privileged member receives the same authorization bootstrap data as an owner/admin.
The fix is to require the same terminal authorization gate on the backend bootstrap routes, e.g. adding ->middleware('can.access.terminal') to both terminal.auth and terminal.auth.ips. The reproduction script applies this middleware as the fixed negative-control behavior and verifies that the member is blocked with HTTP 403 before WebSocket command execution.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.shfrom the bundle root (or withPRUVA_ROOTpointing at the bundle). - The script:
- Reuses the prepared Coolify repository at
<project_cache_dir>/repowhen available. - Checks out
v4.0.0-beta.470and starts the real Laravel application with a SQLite test database. - Starts the real
docker/coolify-realtime/terminal-server.jsWebSocket service. - Creates an owner, a low-privileged
member, a terminal-enabled server record for127.0.0.1, and a local managed-host SSH peer on127.0.0.1:2222. - Logs in as the member through Coolify's magic-link route and makes real HTTP requests to
POST /terminal/auth/ipsandPOST /terminal/auth. - Connects to
ws://127.0.0.1:6002/terminal/wswith the member session cookies and sends a terminal SSH command payload. - Verifies the vulnerable path returns command output from the managed host.
- Applies the fixed middleware to the two terminal bootstrap routes, repeats the same member flow, and verifies the endpoints return 403 and no managed-host command executes.
- Reuses the prepared Coolify repository at
- Expected evidence of reproduction:
bundle/logs/vuln_result.txtshowsIPS_STATUS=200andAUTH_STATUS=200for the member user.bundle/logs/terminal_vuln.logshows WebSocket authentication, authorized IP retrieval, received command, parsed SSH metadata, andSpawning PTY process for terminal session.bundle/logs/managed_host_vuln.logshowsMANAGED_HOST_PROCESSwith the attacker-supplied shell command andMANAGED_HOST_PROCESS_EXIT ... output='REPRO_WS_COMMAND_EXECUTED....bundle/logs/ws_client_vuln.logshows the WebSocket client receivedREPRO_WS_COMMAND_EXECUTED,whoami,hostname, andpwdoutput followed bypty-exited.bundle/logs/fixed_result.txtshowsIPS_STATUS=403andAUTH_STATUS=403for the same member role.
Evidence
Key runtime artifacts from the final successful runs:
bundle/repro/reproduction_steps.sh— self-contained reproducer.bundle/repro/runtime_manifest.json— runtime evidence manifest withservice_started=true,healthcheck_passed=true, andtarget_path_reached=true.bundle/logs/reproduction_steps.log— orchestration log with final summary:Vulnerable: Member endpoints 200/200, WebSocket managed-host command execution=trueFixed: Member endpoints 403/403, WebSocket blocked=trueSUCCESS: CVE-2026-34047 reproduced with WebSocket command execution and fixed negative control
bundle/logs/vuln_result.txt:IPS_STATUS=200IPS_BODY={"ipAddresses":["127.0.0.1","coolify-testing-host","host.docker.internal","localhost"]}AUTH_STATUS=200AUTH_BODY={"authenticated":true}
bundle/logs/terminal_vuln.logproves the real terminal backend path:Websocket client authentication succeeded.Fetched authorized terminal hosts for websocket session.Received websocket message. { ... keys: [ 'command' ] ... }Parsed terminal command metadata. { targetHost: '127.0.0.1', ... }Spawning PTY process for terminal session.PTY process exited. { ... exitCode: 0 ... }
bundle/logs/managed_host_vuln.logproves command execution on the managed-host peer:MANAGED_HOST_PROCESS label=vuln command=" printf 'REPRO_WS_COMMAND_EXECUTED\\n'; whoami; hostname; pwd "MANAGED_HOST_PROCESS_EXIT label=vuln rc=0 output='REPRO_WS_COMMAND_EXECUTED\nvscode\n...\n/tmp\n'
bundle/logs/ws_client_vuln.logproves the WebSocket client received command output:CLIENT_MSG="pty-ready"CLIENT_MSG="REPRO_WS_COMMAND_EXECUTED\r\nvscode\r\n...\r\n/tmp\r\n"CLIENT_MSG="pty-exited"
bundle/logs/fixed_result.txtproves the negative control:IPS_STATUS=403AUTH_STATUS=403- Response message:
Access to terminal functionality is restricted to team administrators.
bundle/logs/ws_client_fixed.logshows the WebSocket handshake fails in the fixed path (Unexpected server response: 500) before any command reaches the managed host.bundle/logs/managed_host_fixed.logcontains only server readiness and noMANAGED_HOST_PROCESS, confirming no command execution in the fixed negative control.
Environment details captured by the script include the vulnerable git commit (575b0766d12bad2a78febff72ab59c017772bcf7 for v4.0.0-beta.470), local PHP version, Coolify Laravel service health, terminal WebSocket readiness, and managed-host SSH readiness.
Recommendations / Next Steps
- Add
can.access.terminalmiddleware to all terminal backend bootstrap routes, not only the visible terminal UI pages. - Keep authorization checks server-side in the WebSocket/bootstrap flow; do not rely on frontend route visibility or UI navigation controls.
- Ensure
/terminal/authand/terminal/auth/ipsenforce the same admin/owner authorization semantics as/terminaland resource terminal pages. - Add regression tests for low-privileged team members covering:
POST /terminal/authreturns 403.POST /terminal/auth/ipsreturns 403./terminal/wsfails closed and never reaches command execution when backend auth routes reject the session.
- Upgrade affected installations to
4.0.0-beta.471or later.
Additional Notes
- Idempotency: The final reproducer was run successfully multiple times. The last two full script executions completed with exit code 0 and reproduced the vulnerable-vs-fixed behavioral difference.
- The managed host is a controlled local SSH peer, which avoids requiring privileged system SSH setup while still proving that attacker input crosses Coolify's real HTTP/WebSocket boundary and reaches the real terminal-server
node-pty/OpenSSH command execution path. - The fixed negative control uses the middleware behavior from the patch to prove the same member session is blocked with 403 before WebSocket command execution.
CVE-2026-34047 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
cat /data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2/bundle/ticket.md{"cwd":"/data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2","duration_ms":3,"exit_code":0,"stderr":"","stdout":"# CVE-2026-34047\n\n## Summary\n\nCoolify terminal WebSocket endpoints lack proper authorization checks, allowing low-privileged members to access terminal functionality and achieve remote command execution on managed hosts.\n\n## Description\n\n## Summary\nCoolify’s terminal WebSocket flow does not enforce the intended `canAccessTerminal` authorization middleware on backend bootstrap routes. An authenticated low-privileged `Member` can directly call the terminal authentication endpoints, obtain authorized host IPs, and use the WebSocket terminal backend to execute commands. In the documented exploit chain, the attacker can also generate an API token and retrieve the team’s SSH private key to achieve root-level command execution on the target host.\n\n## Affected Package\n- **Package:** coollabsio/coolify (Composer)\n- **Vulnerable versions:** <= 4.0.0-beta.470\n- **Patched version:** 4.0.0-beta.471\n\n## Details / Root Cause\nThe frontend `/terminal` page is protected by the `canAccessTerminal` gate, but the backend terminal bootstrap routes (`/terminal/auth` and `/te… [truncated]cat /data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2/bundle/ticket.json | python3 -m json.tool{"cwd":"/data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2","duration_ms":25,"exit_code":0,"stderr":"","stdout":"{\n \"claim\": {\n \"argus_claim_ref\": null,\n \"attacker_control\": \"Authenticated low-privileged Member user\",\n \"claimed_surface\": \"api_remote\",\n \"expected_impact\": \"code_execution\",\n \"finding_id\": null,\n \"id\": null,\n \"required_entrypoint_detail\": \"/terminal/auth/ips, /terminal/auth, and WebSocket /terminal/ws\",\n \"required_entrypoint_kind\": \"endpoint\",\n \"submission_reason\": \"ticket_derived_llm\",\n \"trigger_class\": \"service_api\",\n \"upstream_verdicts\": {\n \"claim_extraction\": {\n \"confidence\": \"high\",\n \"model\": \"accounts/fireworks/models/kimi-k2p7-code\",\n \"reason\": \"Ticket describes authenticated Member calling /terminal/auth/ips and /terminal/auth, then opening WebSocket terminal/ws to execute root commands on managed hosts; final impact is RCE via authorization bypass.\",\n \"source\": \"llm\"\n }\n }\n },\n \"latest_description\": \"## Su… [truncated]ls -la /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/ 2>/dev/null; echo "---repo---"; ls -la /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo 2>/dev/null | head -50; echo "---mirror---"; ls -la /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors 2>/dev/null{"cwd":"/data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2","duration_ms":10,"exit_code":0,"stderr":"","stdout":"total 0\ndrwxr-xr-x 1 vscode vscode 44 Jul 5 06:23 .\ndrwxr-xr-x 1 vscode vscode 5926 Jul 7 23:17 ..\ndrwxr-xr-x 1 vscode vscode 22 Jul 5 06:23 .pruva\ndrwxr-xr-x 1 vscode vscode 1598 Jul 5 05:33 repo\ndrwxr-xr-x 1 vscode vscode 22 Jul 5 05:32 repo-mirrors\n---repo---\ntotal 2176\ndrwxr-xr-x 1 vscode vscode 1598 Jul 5 05:33 .\ndrwxr-xr-x 1 vscode vscode 44 Jul 5 06:23 ..\ndrwxr-xr-x 1 vscode vscode 12 Jul 5 05:33 .agents\ndrwxr-xr-x 1 vscode vscode 32 Jul 5 05:33 .ai\ndrwxr-xr-x 1 vscode vscode 12 Jul 5 05:33 .claude\ndrwxr-xr-x 1 vscode vscode 22 Jul 5 05:33 .codex\n-rw-r--r-- 1 vscode vscode 265 Jul 5 05:33 .coolify-logo\ndrwxr-xr-x 1 vscode vscode 38 Jul 5 05:33 .cursor\n-rw-r--r-- 1 vscode vscode 482 Jul 5 05:33 .dockerignore\n-rw-r--r-- 1 vscode vscode 258 Jul 5 05:33 .editorconfig\n-rw-r--r-- 1 vscode vscode 722 Jul 5 05:33 .env.development.example\n-rw-r--r-- 1 vscode vscode 264 Jul 5 05:33 .env.dusk.ci\n-rw-r--r-- 1 vscode vscode 211 Jul 5 05:33 .env.production\n-rw-r--r-- 1 vscode vscode 2… [truncated]cat /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/.pruva/proof-carry/latest_confirmed/manifest_path 2>/dev/null; ls -la /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/.pruva/ 2>/dev/null; find /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90 -maxdepth 3 -type d 2>/dev/null | head -40{"cwd":"/data/pruva/runs/dabbd08f-334e-4146-96bb-a6bd10db97e2","duration_ms":5,"exit_code":0,"stderr":"","stdout":"total 0\ndrwxr-xr-x 1 vscode vscode 22 Jul 5 06:23 .\ndrwxr-xr-x 1 vscode vscode 44 Jul 5 06:23 ..\ndrwxr-xr-x 1 vscode vscode 60 Jul 5 10:39 proof-carry\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors/coolify.git\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors/coolify.git/hooks\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors/coolify.git/info\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors/coolify.git/objects\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo-mirrors/coolify.git/refs\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo/.git\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo/.git/hooks\n/data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo/.git/info\n/data/pruva/pro… [truncated]cd /data/pruva/project-cache/604602bb-c594-4fc9-8ab9-0e0cf4968a90/repo && git log --oneline -5 && echo "---HEAD---" && git rev-parse HEAD && echo "---branches/tags---" && git tag | grep -i "beta.47" | head -20575b0766d v4.0.0-beta.470 (#9139)
bf306ffad chore: bump version to 4.0.0-beta.470
5c460dd2a fix(proxy): validate stored config matches proxy type (#9146)
4f1f8713e fix(parsers): preserve ${VAR} references in compose instead of resolving to DB values (#9147)
6a14a12a5 fix(parsers): preserve ${VAR} references in compose instead of resolving to DB values
---HEAD---
575b0766d12bad2a78febff72ab59c017772bcf7
---branches/tags---Artifacts and Evidence for CVE-2026-34047
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-34047
Upgrade coollabsio/coolify · Composer to 4.0.0-beta.471 or later.
FAQ: CVE-2026-34047
How does the Coolify terminal exploit chain work?
Which Coolify versions are affected by CVE-2026-34047, and where is it fixed?
How severe is CVE-2026-34047?
How can I reproduce CVE-2026-34047?
References for CVE-2026-34047
Authoritative sources for CVE-2026-34047 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.