← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-39987 · CWE-306 · Disclosed 2026-04-09

marimo is a reactive Python notebook

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a locked lab door with an unguarded side entrance straight into the server terminal

CVE-2026-39987 is a missing-authentication flaw in marimo's terminal WebSocket endpoint, /terminal/ws, letting a remote attacker get an interactive shell without credentials. Authoritative sources put the affected range at versions before 0.23.0; the vendor release notes further narrow practical exposure to editable notebook deployments that are network-reachable, especially marimo edit --host 0.0.0.0, not app/run mode.

In pure technical terms the vendor's *critical* label is fair: this is pre-auth remote code execution with no user interaction. In fleet reality, severity comes down a notch because the vulnerable population is a subset of marimo installs: you need marimo deployed in edit mode, reachable over a network, and not fronted by stronger external auth. KEV listing and confirmed exploitation slam the brakes on any further downgrade, so this stays HIGH for enterprise patching priority.

"One-request unauth shell, but only on exposed edit-mode marimo; KEV and live exploitation keep this out of backlog territory."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Find a reachable edit-mode marimo server

Attackers scan for marimo instances exposed on internet- or shared-network-reachable interfaces, then fingerprint reachable notebook servers. In practice this is now commoditized: scanning coverage exists and detection content has already landed in community tooling.
Conditions required:
  • A marimo server is bound to a reachable interface such as 0.0.0.0
  • The instance is deployed in notebook edit mode rather than app/run mode
  • Network path to the service exists from the attacker
Where this breaks in practice:
  • Most enterprises do not intentionally expose notebook editors to the public internet
  • Reverse proxies, VPNs, private subnets, or bastions often remove unauthenticated reachability
  • App/run mode is not affected
Detection/coverage: Coverage is decent for exposure discovery: ProjectDiscovery nuclei templates added a template for this CVE, but generic infra scanners may miss private or ephemeral dev containers.
STEP 02

Open the terminal WebSocket without auth

The weaponized step is a direct WebSocket connection to /terminal/ws, typically via a custom Python exploit using websocket-client-style logic. The bug is simple and ugly: the route enforced mode/platform checks but skipped the auth validation used by other WebSocket endpoints.
Conditions required:
  • The vulnerable code path exists (marimo < 0.23.0)
  • The terminal endpoint is reachable from the attacker
  • The attacker can complete a WebSocket handshake
Where this breaks in practice:
  • WAFs and reverse proxies sometimes block or constrain WebSocket upgrades
  • Some orgs front marimo with stronger SSO/auth proxies, which removes the exposed path
  • Non-default hardened deployments may never expose the endpoint externally
Detection/coverage: Version-only scanners will flag package presence, but exploit-attempt visibility depends on HTTP/WebSocket logging at the proxy or app layer. Many teams lack good logging for WebSocket upgrade paths.
STEP 03

Land an interactive shell

Successful exploitation yields a PTY shell with the privileges of the marimo process; in containers that is often enough for immediate secrets access and post-exploitation. Reports around this CVE describe attackers executing commands within minutes, with no login token and no user interaction.
Conditions required:
  • WebSocket auth bypass succeeds
  • The marimo process can spawn or expose a usable shell environment
Where this breaks in practice:
  • Least-privilege containers, read-only filesystems, or stripped-down runtimes reduce post-exploitation options
  • EDR/runtime sensors may catch suspicious child processes or reverse shells
  • Process-level privilege may be non-root in better-managed deployments
Detection/coverage: Good runtime telemetry should catch shell spawning from the marimo/Python parent process, outbound callbacks, and access to .env, SSH keys, or cloud credential files.
STEP 04

Harvest secrets from the notebook host

The practical blast radius is usually not the host itself; it's the secrets co-located with the data science stack. Sysdig observed theft attempts against .env files, SSH keys, and cloud credentials, which is exactly why dev tooling RCEs age badly in real environments.
Conditions required:
  • Useful secrets are stored on disk or in environment variables
  • The marimo host has access to internal services, buckets, databases, or model infrastructure
Where this breaks in practice:
  • Short-lived credentials, isolated service accounts, and locked-down IAM sharply reduce payoff
  • Secret managers with no local material on disk frustrate smash-and-grab collection
  • Segmented egress can block immediate exfiltration or follow-on pivots
Detection/coverage: Hunt for reads of .env, ~/.ssh, cloud credential files, and unusual outbound connections shortly after WebSocket activity.
STEP 05

Pivot into internal infrastructure

Once secrets are taken, attackers move beyond the notebook: databases, Redis, SSH bastions, cloud APIs, and model-serving infrastructure are natural next steps. Sysdig later documented multi-stage post-exploitation and malware delivery linked to this entry point.
Conditions required:
  • Compromised credentials or network adjacency provide a second hop
  • The marimo host has meaningful trust relationships inside the environment
Where this breaks in practice:
  • This is post-initial-access and depends on what the notebook can actually reach
  • MFA, network segmentation, workload identity, and egress controls can break the chain
  • Not every notebook host has privileged internal adjacency
Detection/coverage: This is where mature controls help most: IAM anomaly detection, database auth telemetry, SSH monitoring, and runtime/EDR detections should light up if the attacker leaves the notebook host.
03 · Intelligence Metadata

The supporting signals.

In the wildYes. NVD marks the CVE as present in CISA KEV, and Sysdig reported first exploitation 9 hours 41 minutes after public disclosure.
KEV statusListed by CISA KEV on 2026-04-23 with due date 2026-05-07 in the NVD change history and KEV reference set.
Proof-of-concept availabilityThe advisory included enough detail for rapid weaponization; Sysdig says exploitation happened before a public PoC was available. Detection/exposure content now exists in ProjectDiscovery nuclei templates, so this is no longer obscure.
EPSS0.8071 from the user-supplied intel, which is extremely high and directionally consistent with KEV status and rapid exploitation.
CVSS / severityUser supplied CVSS v3.1 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). GitHub also shows CVSS v4.0 9.3 Critical. Either way, the technical severity is plainly top-tier.
Affected rangeAuthoritative package advisory says marimo < 0.23.0. The vendor release notes say the practically exposed set is editable notebooks reachable on a shared network or the public internet.
Fixed version0.23.0 fixes the missing auth check on the terminal route. No distro backport evidence was found in the sources reviewed.
Exposure realityThis is not every marimo install. The release notes say run/app mode is not affected, and externally fronted auth proxies likely neutralize the path. That deployment friction is the main reason this is downgraded from fleet-wide CRITICAL to HIGH.
Observed attack activitySysdig later reported 662 exploit events from 11 unique source IPs across 10 countries between 2026-04-11 and 2026-04-14, including reverse shells, credential theft, DNS exfiltration, and malware delivery.
Researcher / reportingThe GitHub advisory credits q1uf3ng as reporter. Vendor fix landed in PR #9098 by mscolnick on 2026-04-08.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to HIGH (8.8/10)

The decisive downward pressure is deployment friction: exploitation requires a reachable edit-mode marimo server, and the vendor explicitly says app/run mode is not affected. The decisive upward pressure is reality: it is KEV-listed and was exploited within hours, so any exposed instance should be treated as a likely initial-access path, not a theoretical bug.

HIGH Technical impact of unauthenticated shell access on an exposed vulnerable instance
HIGH Practical downgrade driver: affected population is narrower than the raw CVSS suggests
MEDIUM Exact internet exposure population across enterprise fleets

Why this verdict

  • Downgrade for reachability friction: the attacker needs a network-reachable marimo server in edit mode; app/run mode is explicitly out of scope, which cuts the exposed population hard.
  • Downgrade for exposure population: marimo is a niche developer/data-science platform, not a ubiquitous enterprise control plane; many 10,000-host environments will have zero or very few installs.
  • Downgrade for control assumptions: if the notebook is behind VPN, SSO proxy, private ingress, or segmentation, the unauthenticated remote precondition disappears.
  • Upgrade back up for exploitation reality: CISA KEV plus exploitation in under 10 hours means exposed instances are not waiting for a sophisticated actor; they're already in commodity-attack territory.
  • Upgrade back up for blast radius: notebook hosts routinely sit near cloud keys, SSH material, databases, and model assets, so one shell often turns into broader environment access.

Why not higher?

I am not calling this CRITICAL for fleet prioritization because the raw CVSS assumes universal reachability that simply is not true here. The exploit chain starts with a big environmental qualifier: exposed, editable marimo notebooks. In a normal enterprise, that is a much smaller slice than 'all hosts running vulnerable software.'

Why not lower?

I am not pushing this down to MEDIUM because the bug is still unauthenticated remote shell on exposed targets, with KEV listing and confirmed exploitation. Once the preconditions are met, there is very little attacker friction left and the post-exploitation value is unusually high for data-science infrastructure.

05 · Compensating Control

What to do — in priority order.

  1. Pull edit-mode marimo off reachable networks — Move any marimo edit deployment behind VPN, private ingress, or a bastion immediately; if it is internet-facing, treat it as an emergency and do this within hours because KEV/active exploitation override the normal HIGH timeline.
  2. Disable or block WebSocket access to /terminal/ws — At the reverse proxy, ingress controller, or firewall layer, explicitly block the terminal WebSocket path until all instances are upgraded. This is the most direct compensating control and should be deployed within hours on exposed systems.
  3. Force external auth in front of notebook editors — Put editable notebook access behind SSO, access proxy, or identity-aware proxy rather than relying on built-in app auth alone. Deploy this within hours for exposed services, and complete coverage well inside the normal 30-day HIGH mitigation window elsewhere.
  4. Hunt for secret access and child-process execution — Review runtime telemetry for shell spawns from marimo/Python, reads of .env or ~/.ssh, and unusual outbound traffic from notebook hosts. Because exploitation is confirmed, start hunting immediately on any host that was exposed after 2026-04-08.
  5. Rotate co-located credentials — If a vulnerable exposed instance existed at any time after disclosure, rotate cloud keys, SSH keys, database passwords, and API tokens reachable from that host. Do this within hours for exposed instances because attacker dwell time in observed cases was measured in minutes.
What doesn't work
  • Relying on marimo's built-in token auth alone does not help on the vulnerable path; the bug is that /terminal/ws skipped auth validation.
  • Waiting for routine vulnerability scans is not enough; first exploitation reportedly happened before CVE-based scanners had caught up.
  • Perimeter-only HTTP rules are weak if they do not understand or block WebSocket upgrades to the terminal endpoint.
06 · Verification

Crowdsourced verification payload.

Run this on the target Linux host or container that may be serving marimo, not from an auditor workstation. Invoke with sudo bash ./check_marimo_cve_2026_39987.sh or bash ./check_marimo_cve_2026_39987.sh if you already have permission to inspect processes; it needs local process visibility and benefits from root for full command-line inspection.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_marimo_cve_2026_39987.sh
# Detects likely exposure to CVE-2026-39987 on Linux hosts.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -u

have_cmd() { command -v "$1" >/dev/null 2>&1; }

verlt() {
  # returns 0 if $1 < $2 using sort -V
  [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" != "$2" ] && [ "$1" != "$2" ]
}

VERSION=""
SOURCE=""

# Try Python import first
for py in python3 python; do
  if have_cmd "$py"; then
    VERSION="$($py - <<'PY' 2>/dev/null
try:
    import marimo
    print(getattr(marimo, '__version__', ''))
except Exception:
    pass
PY
)"
    if [ -n "$VERSION" ]; then
      SOURCE="python-import:$py"
      break
    fi
  fi
done

# Fallback to pip metadata
if [ -z "$VERSION" ]; then
  for pipcmd in "python3 -m pip" "python -m pip" pip3 pip; do
    if sh -c "$pipcmd --version" >/dev/null 2>&1; then
      VERSION="$(sh -c "$pipcmd show marimo 2>/dev/null | awk -F': ' '/^Version:/{print \$2; exit}'")"
      if [ -n "$VERSION" ]; then
        SOURCE="pip-show:$pipcmd"
        break
      fi
    fi
  done
fi

if [ -z "$VERSION" ]; then
  echo "UNKNOWN - marimo package version not found"
  exit 2
fi

# Determine if version is fixed
if verlt "$VERSION" "0.23.0"; then
  PKG_STATE="vulnerable"
else
  echo "PATCHED - marimo version $VERSION ($SOURCE) is >= 0.23.0"
  exit 0
fi

# Inspect processes for risky runtime posture
RISKY_PROC=0
RISKY_DETAILS=""
if have_cmd ps; then
  while IFS= read -r line; do
    cmd="$line"
    echo "$cmd" | grep -E '(^|[ /])marimo([ ]|$)' >/dev/null 2>&1 || continue
    echo "$cmd" | grep -E '([ ]|^)edit([ ]|$)|marimo[ ]+edit' >/dev/null 2>&1 || continue
    if echo "$cmd" | grep -E -- '--host[ =](0\.0\.0\.0|::)| -h[ ]+(0\.0\.0\.0|::)' >/dev/null 2>&1; then
      RISKY_PROC=1
      RISKY_DETAILS="$RISKY_DETAILS\n$cmd"
    elif ! echo "$cmd" | grep -E -- '--host[ =](127\.0\.0\.1|localhost)| -h[ ]+(127\.0\.0\.1|localhost)' >/dev/null 2>&1; then
      # No explicit localhost binding seen; still suspicious for edit mode
      RISKY_PROC=1
      RISKY_DETAILS="$RISKY_DETAILS\n$cmd"
    fi
  done < <(ps -eo args= 2>/dev/null)
fi

# Inspect listening sockets on the common marimo port if possible
PORT2718=0
if have_cmd ss; then
  ss -lntp 2>/dev/null | grep -E '[:.]2718[[:space:]]' >/dev/null 2>&1 && PORT2718=1
elif have_cmd netstat; then
  netstat -lntp 2>/dev/null | grep -E '[:.]2718[[:space:]]' >/dev/null 2>&1 && PORT2718=1
fi

if [ "$RISKY_PROC" -eq 1 ]; then
  echo "VULNERABLE - marimo version $VERSION (<0.23.0) with likely exposed edit-mode process detected.${RISKY_DETAILS}"
  exit 1
fi

if [ "$PORT2718" -eq 1 ]; then
  echo "VULNERABLE - marimo version $VERSION (<0.23.0); listening on common marimo port 2718, exposure should be reviewed immediately"
  exit 1
fi

echo "UNKNOWN - marimo version $VERSION is vulnerable (<0.23.0), but this script could not confirm an exposed edit-mode runtime. Review running processes, ingress, and reverse-proxy paths manually."
exit 2
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: identify every marimo install, then immediately separate edit-mode instances from app/run-mode instances. For any host that was reachable from the internet or a shared network after 2026-04-08, treat it as urgent: because this CVE is KEV-listed and actively exploited, patch / mitigate immediately, within hours, overriding the normal HIGH bucket timing; that means apply a compensating block on network exposure or /terminal/ws access within hours instead of waiting for the standard noisgate mitigation SLA. For the actual upgrade, move all remaining vulnerable marimo < 0.23.0 instances to 0.23.0+ on an accelerated schedule; the default HIGH noisgate remediation SLA is ≤180 days, but exposed edit-mode systems should be upgraded as part of the same immediate response window, not left to ride the long tail.

Sources

  1. NVD CVE-2026-39987
  2. GitHub Advisory GHSA-2679-6mx9-h9xc
  3. marimo 0.23.0 release notes
  4. marimo PR #9098 fix: properly authenticate terminal route
  5. Sysdig: From disclosure to exploitation in under 10 hours
  6. Sysdig: weaponized for blockchain botnet via Hugging Face
  7. Endor Labs: Root in One Request
  8. ProjectDiscovery nuclei templates release
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.