FlawOpen

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

● CVE-2026-84973 · 高
セキュリティ研究 · FlawOpen

CVE-2026-84973:JupyterLab スナップショットワークフローの TOCTOU

秒単位の時刻チェックにより、未承認の Pull Request コミットをスナップショットワークフローが取得・実行し、ジョブの権限を使う可能性がありました。update-snapshots-checkout を 1.0.0 以降に更新してください。

💡 わかりやすい解説 (ELI5)

レビュー担当者は特定の荷物を承認しましたが、受け取り窓口は時計の秒だけを確認します。同じ秒の間に荷物を差し替えると、差し替え品も承認済みに見えてしまいます。この action は秒精度の時刻を比較し、同じ値を許可していたため、メンテナーのコメントと同時に Pull Request の更新が通る可能性がありました。

主要な概念と専門用語

TOCTOU
Time-of-check to time-of-use:セキュリティ判断の対象となったリソースが、実際に使われる前に変化する問題です。
Pull Request の head SHA
Pull Request ブランチ先頭のコミットを示す不変の識別子です。ワークフローは承認された正確なコミットだけを実行すべきです。
GitHub Actions トークン権限
ジョブの GITHUB_TOKEN に許可された操作です。リポジトリの内容や Pull Request の読み書きなどが含まれます。
CWE-367 / CWE-829
アドバイザリは時刻競合を CWE-367、Pull Request 管理下の機能を実行する問題を CWE-829 に分類しています。

フレームワーク対応

根本原因の分析 (Root Cause)

バージョン 1.0.0 より前の update-snapshots-checkout は、Pull Request の pushed_at とトリガーコメントの created_at を取得し、Unix 秒に変換して、pushed_at が厳密に大きい場合だけ実行を拒否していました。API の両時刻は秒単位の精度です。そのため、同じ秒に行われた push と承認コメントは等しくなり、チェックを通過することがありました。その後 action は現在の head SHA を保存して checkout 後に照合しましたが、これは checkout が新しく取得した SHA と一致することしか示さず、メンテナーがそのコミットを承認したことは証明しません。上流修正は -gt を -ge に変更し、同じ時刻とそれ以降の push を拒否します。脆弱な action を使い、後続処理が Pull Request の内容を実行するワークフローが対象です。影響はワークフローの権限と資格情報に依存します。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

信頼されたメンテナーがワークフローを起動

Pull Request が作成され、OWNER、COLLABORATOR、または MEMBER がコメントを投稿してスナップショット更新ワークフローを起動します。

ステップ 2

攻撃者が管理する更新がコメントと同じ秒に発生

投稿者が同じ秒に Pull Request ブランチを更新します。API は秒精度のため、Unix 秒に変換した値が等しくなることがあります。

ステップ 3

厳密な大なり比較が同じ値を許可

脆弱な action は pushed_at が created_at より大きい場合だけ拒否します。同じ値は条件に一致せず、新しい head SHA が保存されます。

ステップ 4

ワークフローがそのコミットを取得して実行する可能性

checkout は保存された SHA と一致するため後続チェックを通過します。後続ステップが Pull Request 管理下の action やスクリプトを実行すると、ジョブの権限と資格情報が使われます。

ステップ 5

更新し、ワークフロー権限を絞る

1.0.0 以降へ更新します。action を不変 SHA に固定し、GITHUB_TOKEN に必要最小限の権限だけを与え、未信頼コードを秘密情報や特権ジョブから隔離します。

ソースコード比較:脆弱 vs 堅牢化

これは上流の一行修正です。-ge は API の時刻精度で同じ値も拒否し、同一秒の回避を防ぎます。それでも承認は不変 SHA に結び付け、権限を最小化してください。時刻順だけでは不変の認可判断になりません。 upstream の diff では既存の診断メッセージは変更されません。メッセージは「後」としていますが、修正後は同時刻も拒否されます。

脆弱な実装
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
堅牢化されたセキュアパッチ
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

エンジニアリング&システム堅牢化チェックリスト

参考資料