← back
CVE-2026-39987criticalunder attackCWE-306

marimo Affected by Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass

100Vexday Risk Score

Patch now. It under exploitation confirmed by CISA and has a working public exploit.

ssvc Actcvss 9.3epss 97%
from disclosure to weapon4 days
Published on NVDApr 9
1st PoC+4d
CISA KEV+14d
exploitation probability
97%top 1% of all CVEs
observed exploitation
yesCISA + VulnCheck
16 public exploit(s)
Action required by CISAfederal deadline: 2026-05-07

Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.

Summary

O endpoint WebSocket /terminal/ws do marimo (notebook Python reativo) aceita conexões sem qualquer verificação de autenticação, enquanto o endpoint /ws equivalente valida corretamente o token de acesso. Isso permite que um atacante não autenticado abra uma sessão PTY completa e execute comandos arbitrários no host — em containers Docker padrão, como root. O impacto real depende de o marimo estar rodando em modo 'edit' e acessível pela rede; notebooks publicados como aplicação ('run' mode) não são afetados.

Technical detail

A falha é CWE-306 (Missing Authentication for Critical Function). Em marimo/_server/api/endpoints/terminal.py, o handler do WebSocket /terminal/ws verifica apenas se a sessão está em SessionMode.EDIT e se a plataforma suporta terminal (supports_terminal()) antes de chamar websocket.accept() e, na sequência, pty.fork() para criar um shell interativo. Nenhuma chamada a validate_auth() existe nesse caminho. O endpoint /ws, por comparação, instancia um WebSocketConnectionValidator e chama validate_auth() antes de aceitar a conexão — exatamente o passo que falta no terminal.

O marimo usa o AuthenticationMiddleware do Starlette, que marca uma conexão sem credenciais como UnauthenticatedUser mas não a rejeita automaticamente; a aplicação real do controle de acesso depende de cada endpoint chamar validate_auth() ou usar o decorator @requires(). Como /terminal/ws não tem nenhum dos dois, o middleware global não protege essa rota — é uma falha de implementação pontual, não uma falha estrutural do framework de autenticação como um todo.

O atacante não controla nenhum parâmetro de entrada sofisticado: basta abrir a conexão WebSocket. A partir daí, o texto enviado é interpretado como entrada de terminal (PTY), então qualquer comando de shell é executado com os privilégios do processo do servidor marimo — root, nos exemplos de reprodução em Docker publicados no advisory.

How it’s exploited

O vetor é uma conexão WebSocket direta a ws://alvo:porta/terminal/ws, sem cabeçalho de autenticação, sem token e sem interação do usuário. A complexidade é trivial: uma única conexão aceita retorna um shell PTY completo, com saída de terminal em texto simples — o PoC público do advisory apenas conecta, drena a saída inicial e envia comandos como 'id\n' para confirmar execução como root.

As pré-condições reais que a manchete 'RCE pré-autenticação' esconde: o servidor precisa estar em modo edit (marimo edit), não em modo de aplicação publicada (run mode, que não expõe esse endpoint com o mesmo efeito); e precisa estar acessível pela rede do atacante — isso cobre cenários como marimo edit --host 0.0.0.0, instâncias expostas em rede compartilhada, ou deploys em modo edit que dependem apenas do token de acesso embutido do marimo (marimo edit --token) para segurança, já que esse token nunca é checado nesse endpoint.

A CVE está no catálogo KEV da CISA (adicionada em 2026-04-23, prazo de correção 2026-05-07), confirmando exploração ativa observada. Há template Nuclei público e reportes de pesquisadores (Sysdig) descrevendo o intervalo entre divulgação e exploração em produção como inferior a 10 horas, o que indica varredura automatizada em massa assim que o detalhe técnico ficou público.

Versions

Affected
marimo <= 0.20.4, segundo o advisory oficial (GHSA-2679-6mx9-h9xc). A descrição da CVE publicada pelo fornecedor, porém, afirma 'prior to 0.23.0' de forma mais ampla — há uma divergência não resolvida entre as duas fontes oficiais sobre se versões entre 0.20.4 e 0.23.0 também estavam vulneráveis; trate qualquer versão anterior a 0.23.0 como potencialmente exposta até confirmação em changelog específico.
Fixed in
0.23.0 (correção aplicada no commit c24d480, PR #9098, que adiciona a chamada a validate_auth() no endpoint /terminal/ws). Não há menção de backport para ramos anteriores no material revisado.

How to protect

A correção definitiva é atualizar para marimo 0.23.0 ou posterior, onde /terminal/ws passa a chamar validate_auth() da mesma forma que /ws, retornando WebSocketCodes.UNAUTHORIZED quando a autenticação falha ou o token é inválido.

Se a atualização não for possível de imediato, o paliativo real é de rede, não de configuração do marimo: não expor instâncias em modo edit em interfaces além de 127.0.0.1 (evitar --host 0.0.0.0 sem controle adicional), e restringir acesso à porta do marimo via firewall, VPN ou proxy autenticado que filtre o caminho /terminal/ws antes de chegar à aplicação. Rodar os notebooks apenas em modo de aplicação publicada (run mode), quando o caso de uso permitir, remove a superfície mais crítica, já que esse modo não expõe o mesmo caminho de execução de shell.

O que não funciona como mitigação: confiar no token de acesso embutido do marimo (--token) ou assumir que o AuthenticationMiddleware do Starlette protege todos os WebSockets — como o advisory demonstra, esse middleware não rejeita conexões sozinho, e o endpoint do terminal simplesmente nunca consultava o resultado da autenticação. Também não há flag de configuração documentada para desabilitar seletivamente o terminal sem aplicar o patch.

How to detect

Em logs do servidor marimo ou de proxy/reverse-proxy na frente dele, procure handshakes WebSocket para o caminho /terminal/ws sem o parâmetro access_token esperado, ou vindos de IPs fora da faixa de administradores conhecidos. No nível de processo, monitore o processo marimo criando processos-filho via pty.fork() (novos PTYs/shells) sem correlação com uma sessão de edição legítima no navegador do operador — isso é o sinal mais direto de exploração bem-sucedida.

Como o exploit é uma única conexão WebSocket com payload textual simples (comandos de shell em texto puro), não há assinatura de rede complexa: qualquer ferramenta de scanning que teste a existência do endpoint /terminal/ws e envie um comando de teste (como 'id') é suficiente para comprometer um alvo vulnerável, e o template Nuclei público automatiza exatamente esse teste — presença desse tipo de requisição nos logs de acesso indica tentativa, não necessariamente sucesso, que só se confirma pela criação do PTY no host.

Researched and written with AI from the vendor advisory and public analysis, with the sources above. Always confirm the fixed version in the official advisory before acting.
marimo is a reactive Python notebook. Prior to 0.23.0, Marimo has a Pre-Auth RCE vulnerability. The terminal WebSocket endpoint /terminal/ws lacks authentication validation, allowing an unauthenticated attacker to obtain a full PTY shell and execute arbitrary system commands. Unlike other WebSocket endpoints (e.g., /ws) that correctly call validate_auth() for authentication, the /terminal/ws endpoint only checks the running mode and platform support before accepting connections, completely skipping authentication verification. This vulnerability is fixed in 0.23.0.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Affected products
marimo-team · marimo
⚠ Public resources, to assess the exposure of systems you control or are authorized to test. Test only with authorization.