FlawOpen

flawopen.com/Teardowns/cve-2026-84973-jupyterlab-snapshot-checkout-toctou

● CVE-2026-84973 · Alta
Pesquisa · FlawOpen

CVE-2026-84973: condição TOCTOU no fluxo de snapshots do JupyterLab

Uma verificação de horário com precisão de um segundo podia permitir que o fluxo de snapshots baixasse e executasse um commit não aprovado de pull request com as permissões do job. Atualize update-snapshots-checkout para a versão 1.0.0 ou posterior.

💡 Explicação em Linguagem Simples (ELI5)

Uma pessoa aprova uma encomenda específica, mas o balcão de entrega confere apenas o segundo do relógio. Se alguém trocar a encomenda naquele mesmo segundo, a substituta parece aprovada. A action comparava horários com precisão de um segundo e aceitava valores iguais, permitindo que uma atualização do pull request passasse junto do comentário do mantenedor.

Conceitos Centrais e Termos

TOCTOU
Time-of-check to time-of-use: a decisão de segurança é tomada sobre um recurso que muda antes de ser utilizado.
SHA do head do pull request
Identificador imutável do commit no topo do branch do pull request. O fluxo deve executar apenas o commit exato autorizado.
Permissões do token do GitHub Actions
Operações permitidas ao GITHUB_TOKEN do job, como ler ou gravar conteúdo e pull requests.
CWE-367 / CWE-829
O advisory associa a condição de corrida temporal à CWE-367 e a execução de conteúdo controlado pelo pull request à CWE-829.

Mapeamentos de frameworks

Análise de Causa Raiz

Antes da versão 1.0.0, update-snapshots-checkout obtinha os horários pushed_at do pull request e created_at do comentário que acionou o fluxo, convertia ambos para segundos desde a época e rejeitava a execução apenas quando pushed_at era estritamente maior. Os dois horários da API têm precisão de um segundo. Assim, um push e um comentário autorizado no mesmo segundo podiam ser iguais e passar pela verificação. Em seguida, a action salvava o SHA atual do head e fazia checkout; a comparação posterior apenas provava que o checkout correspondia ao SHA recém-obtido, não que o mantenedor havia autorizado aquele commit. A correção upstream troca -gt por -ge e rejeita horários iguais ou posteriores. O risco exige uma versão vulnerável e etapas posteriores que executem conteúdo do pull request; o impacto depende das permissões e credenciais do workflow.

Fluxo de Ataque Passo a Paso

Passo 1

Um mantenedor autorizado aciona o fluxo

Um pull request é aberto e um OWNER, COLLABORATOR ou MEMBER publica o comentário que inicia o fluxo de atualização de snapshots.

Passo 2

Uma atualização controlada pelo atacante corre contra o comentário

Um colaborador atualiza o branch do pull request no mesmo segundo. Como os horários da API têm precisão de um segundo, os valores convertidos podem ser iguais.

Passo 3

A comparação estrita aceita a igualdade

A action vulnerável rejeita apenas quando pushed_at é maior que created_at. Valores iguais não atendem à condição, então o novo SHA do head é registrado.

Passo 4

O workflow baixa e pode executar esse commit

O checkout corresponde ao SHA registrado e passa na comparação posterior. Se etapas seguintes executarem actions ou scripts controlados pelo pull request, elas terão as permissões e credenciais configuradas no job.

Passo 5

Atualize e limite a autoridade do workflow

Atualize para 1.0.0 ou posterior. Fixe as actions em SHAs imutáveis, conceda somente as permissões necessárias ao GITHUB_TOKEN e mantenha código de pull requests não confiáveis longe de segredos e jobs privilegiados.

Código-Fonte: Vulnerável vs. Seguro

Este trecho corresponde à correção upstream de uma linha: -ge rejeita igualdade na precisão de tempo fornecida pela API. Ele fecha o bypass documentado no mesmo segundo. Ainda prefira autorizar um SHA imutável e reduza permissões, pois a ordem temporal não substitui uma decisão de autorização imutável. O diff upstream mantém a mensagem de diagnóstico existente; ela diz “depois”, embora a igualdade agora também seja rejeitada.

IMPLEMENTAÇÃO VULNERÁVEL
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
    echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
    exit 1
fi
PATCH SEGURO E ROBUSTO
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
    echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
    exit 1
fi

Lista de Verificação de Segurança para Engenharia

Fontes