flawopen.com/Teardowns/cve-2026-84973-jupyterlab-snapshot-checkout-toctou
CVE-2026-84973:JupyterLab スナップショットワークフローの TOCTOU
秒単位の時刻チェックにより、未承認の Pull Request コミットをスナップショットワークフローが取得・実行し、ジョブの権限を使う可能性がありました。update-snapshots-checkout を 1.0.0 以降に更新してください。
レビュー担当者は特定の荷物を承認しましたが、受け取り窓口は時計の秒だけを確認します。同じ秒の間に荷物を差し替えると、差し替え品も承認済みに見えてしまいます。この 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 の内容を実行するワークフローが対象です。影響はワークフローの権限と資格情報に依存します。
ステップ・バイ・ステップの攻撃フロー
信頼されたメンテナーがワークフローを起動
Pull Request が作成され、OWNER、COLLABORATOR、または MEMBER がコメントを投稿してスナップショット更新ワークフローを起動します。
攻撃者が管理する更新がコメントと同じ秒に発生
投稿者が同じ秒に Pull Request ブランチを更新します。API は秒精度のため、Unix 秒に変換した値が等しくなることがあります。
厳密な大なり比較が同じ値を許可
脆弱な action は pushed_at が created_at より大きい場合だけ拒否します。同じ値は条件に一致せず、新しい head SHA が保存されます。
ワークフローがそのコミットを取得して実行する可能性
checkout は保存された SHA と一致するため後続チェックを通過します。後続ステップが Pull Request 管理下の action やスクリプトを実行すると、ジョブの権限と資格情報が使われます。
更新し、ワークフロー権限を絞る
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
エンジニアリング&システム堅牢化チェックリスト
- ✓update-snapshots-checkout のすべての利用を 1.0.0 以降へ更新し、コピーや fork も確認します。
- ✓外部および組織内の action を確認済みの不変 SHA に固定し、更新前に差分をレビューします。
- ✓時刻だけで可変な Pull Request ブランチを承認せず、正確な head SHA に承認を結び付けます。
- ✓各ジョブに明示的な最小権限を設定し、不要なら contents: write と pull-requests: write を付与しません。
- ✓外部 Pull Request のコードを実行するジョブから秘密情報や特権デプロイ資格情報を分離します。
- ✓同じ時刻と後の時刻の両方を拒否する回帰テストを追加します。