Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-39987-Lab — Local Docker lab reproducing CVE-2026-39987, a pre-auth RCE in marimo's terminal WebSocket. Compares vulnerable and patched versions with least-harm PoC scripts for educational security research. | Kitploit
Tools/GitHubGitHub/rootdirective-sec/cve-2026-39987-lab
Container SecurityVulnerability AnalysisExploitationWeb SecurityLearning & EducationLabs & Practice
GitHubrootdirective-sec/cve-2026-39987-lab

CVE-2026-39987-Lab

Local Docker lab reproducing CVE-2026-39987, a pre-auth RCE in marimo's terminal WebSocket. Compares vulnerable and patched versions with least-harm PoC scripts for educational security research.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
194 months agoNot yet reviewed

CVE-2026-39987 — marimo Pre-Auth Terminal WebSocket RCE Lab

Local-only Docker lab for reproducing and understanding CVE-2026-39987 in marimo. This project compares a vulnerable marimo service with a patched marimo service side-by-side, then demonstrates the difference using a least-harm proof script.


Executive Summary

CVE-2026-39987 is a critical pre-authentication remote code execution vulnerability in marimo, a reactive Python notebook framework.

The vulnerable behavior exists in the terminal WebSocket endpoint:

root@kitploit:~
/terminal/ws

In affected versions, this endpoint can be reached without valid authentication and can create an interactive terminal session. An unauthenticated attacker who can reach a vulnerable marimo edit server may execute commands with the privileges of the marimo process.

This lab reproduces the vulnerability in a controlled local Docker environment:

ServiceVersionURLExpected behavior
vulnmarimo 0.20.4http://127.0.0.1:8081/terminal/ws accepts unauthenticated WebSocket connection
patchedmarimo 0.23.0http://127.0.0.1:8082/terminal/ws rejects unauthenticated WebSocket connection with 403 Forbidden

The goal is not to provide a weaponized exploit. The goal is to show the observable security difference between vulnerable and patched versions using local-only, reproducible evidence.


Vulnerability Overview

Affected component

The affected component is marimo's terminal WebSocket endpoint:

root@kitploit:~
/terminal/ws

This endpoint is used by marimo's edit environment to provide terminal functionality through a browser-connected WebSocket session.

Root cause

The vulnerable endpoint accepted WebSocket connections without enforcing the same authentication checks expected for protected marimo edit functionality.

The important difference is:

root@kitploit:~
vulnerable behavior:
  unauthenticated client can connect to /terminal/ws
  terminal session is created
  commands can be sent through the WebSocket

patched behavior:
  unauthenticated client is rejected
  WebSocket handshake fails with 403 Forbidden
  terminal session is not created

The patch adds authentication validation to the terminal WebSocket flow before allowing a terminal session to be established.

Impact

If a vulnerable marimo edit server is exposed to a reachable network, an unauthenticated attacker may execute commands as the user running the marimo process.

In this lab, the marimo process is intentionally run as a low-privilege non-root user:

root@kitploit:~
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)

This keeps the demonstration safer while still proving the vulnerability.


Lab Goals

This lab is designed to prove three things:

  1. The vulnerable version accepts unauthenticated WebSocket connections to /terminal/ws.
  2. A benign command can be executed through that unauthenticated terminal WebSocket.
  3. The patched version rejects the same unauthenticated WebSocket attempt with 403 Forbidden.

The lab intentionally avoids destructive commands, persistence, reverse shells, credential dumping, or any internet-facing target.


Repository Structure

root@kitploit:~
.
├── docker-compose.yml
├── vuln/
│   ├── Dockerfile
│   └── notebook.py
├── patched/
│   ├── Dockerfile
│   └── notebook.py
├── poc/
│   ├── poc.py
│   ├── rce_poc.py
│   └── requirements.txt
├── SAFETY.md
├── README.md
└── .gitignore

Important files


Lab Architecture

root@kitploit:~
Host machine

127.0.0.1:8081  ─────►  vuln container
                         marimo 0.20.4
                         /terminal/ws accepts unauthenticated WebSocket

127.0.0.1:8082  ─────►  patched container
                         marimo 0.23.0
                         /terminal/ws rejects unauthenticated WebSocket

Both services expose marimo's internal port 2718, but the host ports are different:

root@kitploit:~
vuln    -> 127.0.0.1:8081
patched -> 127.0.0.1:8082

The services are bound to 127.0.0.1 only. They are not intended to be exposed to a LAN or the internet.


Docker Hardening Notes

The containers are configured with several guardrails where possible:

root@kitploit:~
- bind ports to 127.0.0.1 only
- run as a non-root user
- drop Linux capabilities
- enable no-new-privileges
- use a read-only root filesystem
- provide only limited tmpfs write locations
- isolate services inside a dedicated Docker bridge network

These controls do not remove the vulnerability from the vulnerable service. They reduce the blast radius of the local demonstration.


Prerequisites

Tested assumptions:

root@kitploit:~
- Linux x86_64 or macOS with Docker Desktop
- Docker Compose v2
- Python 3.9+
- Localhost-only testing

Required tools:

root@kitploit:~
docker --version
docker compose version
python3 --version

Setup

Clone or enter the project directory:

root@kitploit:~
cd cve-2026-39987

Build and start both services:

root@kitploit:~
docker compose up -d --build

Check that both containers are running:

root@kitploit:~
docker compose ps

Expected services:

root@kitploit:~
cve-2026-39987-vuln
cve-2026-39987-patched

Verify Versions

Check the vulnerable service version:

root@kitploit:~
docker compose exec vuln marimo --version

Expected:

root@kitploit:~
0.20.4

Check the patched service version:

root@kitploit:~
docker compose exec patched marimo --version

Expected:

root@kitploit:~
0.23.0

Install PoC Dependencies

Create a Python virtual environment:

root@kitploit:~
python3 -m venv .venv
source .venv/bin/activate

Install dependencies:

root@kitploit:~
python -m pip install -r poc/requirements.txt

Least-Harm PoC

The primary proof script is:

root@kitploit:~
poc/poc.py

It attempts to connect to:

root@kitploit:~
/terminal/ws

Then it sends only benign proof commands:

root@kitploit:~
id
whoami
hostname

No reverse shell, file write, persistence, credential access, or destructive command is used.

Test vulnerable service

Run:

root@kitploit:~
python poc/poc.py --base-url http://127.0.0.1:8081

Expected result:

root@kitploit:~
[*] Target WebSocket: ws://127.0.0.1:8081/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[+] websocket connected without credentials
[result] VULNERABLE: unauthenticated command execution observed
[proof]
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>

If the script prints raw terminal output but includes this block, the vulnerability is still confirmed:

root@kitploit:~
CVE39987_PROOF_START
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
CVE39987_PROOF_END

That means the WebSocket connection succeeded and the command output came back from the terminal session.

Test patched service

Run:

root@kitploit:~
python poc/poc.py --base-url http://127.0.0.1:8082

Expected result:

root@kitploit:~
[*] Target WebSocket: ws://127.0.0.1:8082/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[-] websocket connection rejected/failed: Handshake status 403 Forbidden
[result] not exploitable by this unauthenticated check

This shows the patched service rejects unauthenticated access before creating a terminal session.


Real-Time Learning Client

The file below is intended for local learning only:

root@kitploit:~
poc/rce_poc.py

It demonstrates the same issue in a more real-time way by connecting to the vulnerable terminal WebSocket and allowing terminal interaction.

Use this only against the local Docker lab.

Run against vulnerable service

root@kitploit:~
python poc/rce_poc.py --base-url http://127.0.0.1:8081

Expected behavior:

root@kitploit:~
[+] Connected successfully (Pre-Auth RCE)
✓ Interactive shell is ready to use!

Safe commands to try:

root@kitploit:~
id
whoami
hostname
pwd
python -c 'import marimo; print(marimo.__version__)'
exit

Run against patched service

root@kitploit:~
python poc/rce_poc.py --base-url http://127.0.0.1:8082

Expected behavior:

root@kitploit:~
[-] Connection Failed: 403 Forbidden
[!] This is likely the PATCHED version.
    The WebSocket endpoint is now protected.

Safety note for real-time mode

The real-time client exists to help understand how the vulnerable WebSocket behaves. It should not be used against any system outside this local lab.

For portfolio publication, the recommended primary evidence is still poc/poc.py, because it is bounded, repeatable, and least-harm.


Expected Security Difference

The main lab result should be summarized as:

root@kitploit:~
vuln / marimo 0.20.4:
  unauthenticated WebSocket handshake to /terminal/ws succeeds
  benign command output is observable

patched / marimo 0.23.0:
  unauthenticated WebSocket handshake to /terminal/ws fails
  server returns 403 Forbidden
  no terminal session is created

This is the core proof for the CVE reproduction.


Manual WebSocket Check

If you want to manually inspect the behavior, use a WebSocket-capable client and connect to:

root@kitploit:~
ws://127.0.0.1:8081/terminal/ws
ws://127.0.0.1:8082/terminal/ws

Expected:

root@kitploit:~
8081 -> connection accepted
8082 -> 403 Forbidden

The PoC scripts are preferred because they produce clearer evidence.


Detection and Monitoring Ideas

In a real environment, useful indicators include:

root@kitploit:~
- WebSocket requests to /terminal/ws
- unauthenticated access attempts to marimo edit servers
- unexpected terminal sessions spawned by marimo
- commands launched by the marimo process
- access to .env, SSH keys, cloud credentials, or notebook secrets
- outbound network traffic shortly after /terminal/ws connections

Example local artifacts to inspect:

root@kitploit:~
docker compose logs vuln
docker compose logs patched
docker compose ps
docker compose exec vuln ps aux

Cleanup

Stop and remove containers:

root@kitploit:~
docker compose down

Remove volumes as well:

root@kitploit:~
docker compose down -v

Remove local Python virtual environment if desired:

root@kitploit:~
rm -rf .venv

Safety Rules

This repository is for local security research and portfolio demonstration only.

Allowed:

root@kitploit:~
- localhost testing
- Docker-only reproduction
- benign proof commands such as id, whoami, hostname
- comparing vulnerable and patched behavior
- documenting root cause and detection ideas

Not allowed:

root@kitploit:~
- testing against public marimo servers
- testing systems you do not own or administer
- reverse shells
- persistence
- credential theft
- destructive commands
- lateral movement
- botnet or malware behavior

Disclaimer

This project is intended only for authorized local testing and defensive security education. Do not use these scripts against systems you do not own or have explicit permission to test.


References

  • GitHub Security Advisory — GHSA-2679-6mx9-h9xc:
    https://github.com/advisories/GHSA-2679-6mx9-h9xc

  • NVD — CVE-2026-39987:
    https://nvd.nist.gov/vuln/detail/CVE-2026-39987

  • marimo upstream repository:
    https://github.com/marimo-team/marimo

  • Patch commit — add authentication validation to terminal WebSocket:
    https://github.com/marimo-team/marimo/commit/c24d4806398f30be6b12acd6c60d1d7c68cfd12a

  • Patch PR — marimo PR #9098:
    https://github.com/marimo-team/marimo/pull/9098

Download Tool
FilePurpose
docker-compose.ymlDefines vulnerable and patched marimo services
vuln/DockerfileBuilds the vulnerable marimo service
patched/DockerfileBuilds the patched marimo service
vuln/notebook.pyMinimal marimo notebook file used by the vulnerable service
patched/notebook.pyMinimal marimo notebook file used by the patched service
poc/poc.pyLeast-harm proof script that runs benign commands only
poc/rce_poc.pyReal-time learning client for observing terminal behavior in the local lab
poc/requirements.txtPython dependencies for PoC scripts
SAFETY.mdSafety rules and scope boundaries