TT Lab
はじめる
学ぶ 学習パス コース

ビルドは緑だったのに、あのライブラリを入れたのは誰か

自分で選んだのは7つ、入ったのは25個

TT Labで続きを見る

一言でいうと

依存関係の一覧は、私たちが書いたファイル(package.json)ではなく、実際にインストールされたものを書き留めたファイル(package-lock.json)にあります。その2つの個数の差が、サプライチェーンセキュリティが難しい理由のほとんどすべてです。

なぜ必要なのか

事故が起きた翌朝に最初に来る質問は、いつも同じです。「うちはそれを使っていますか」。この質問が難しいのは、答えが複雑だからではなく、答えるための資料がどこにもないからです。リポジトリには、私たちが手で書いた依存の一覧があります。ところが、そこに書かれた名前はそれぞれ別の名前を引き込み、その名前がまた別の名前を引き込みます。5行が25個になるのに、特別な事件は必要ありません。ただインストールすれば、そうなります。

そのため、「何が入っているか」は、ビルドが終わったあとにもう一度尋ねるべき質問になります。ソースをいくら読んでも、答えは出ません。答えはビルドの時点で決まり、その瞬間を過ぎると消えてしまいます。ロックファイル(lockfile)とSBOMは、その瞬間をつかまえておくために作られたものです。

もう1つ、分けなければならないことがあります。インストールされたものが、すべてデプロイに載るわけではありません。テストツール、バンドラー、リンターはビルドマシンでだけ動き、ユーザーには届きません。この2つをひとまとめに数えると、「直すべきもの」の数が2倍に膨らみ、膨らんだ数は誰も見ません。

どう動くのか

ロックファイルは、「何を受け取ったか」を固定します。npmのpackage-lock.jsonは、lockfileVersion 3でpackagesというマップを使いますが、インストール先をキーとし、ルートプロジェクトは空文字列のキーで入ります。各項目には、version、取得先を書いたresolved、そして受け取ったバイト列がそれで正しいことを保証するintegrityがあります。開発専用のツリーにだけ属する項目には、devがtrueとして付きます(npmのドキュメント)。個数を数えるときにルート項目を除かず、1つ多く出てしまうミスが、ここで最もよくあります。

SBOMは、その一覧をツールや組織をまたいで持ち運べる形式に移したものです。広く使われている形式が2つあります。CycloneDXはOWASPとEcmaが共同で管理し、ECMA-424として標準化されており、コンポーネントとサービス、そして直接依存と推移的依存の両方を含む依存グラフを表現します(CycloneDXの概要)。JSONドキュメントの最初の2つのフィールドはbomFormatとspecVersionですが、bomFormatの値が"CycloneDX"の1つだけに固定されている理由は興味深いものです。BOMファイルには名前の規則がなく、JSONスキーマには名前空間がないので、ファイルを見るだけで、これが何の形式かわかる必要があるからです(CycloneDX 1.6 JSON)。

SPDXは、同じことを別の語彙で行います。パッケージごとに、名前とバージョン、チェックサム、そしてpurlのような外部参照を書き、要素間の関係をDEPENDS_ON・CONTAINSのような関係名で表現します(SPDX 2.3 Package Information)。どちらを使っても、実際に重要な欄は3つです。何が対象か、何が入っているか、そして何が何を引き込んだかです。最後の欄がないSBOMは、平らな一覧にすぎず、「これはなぜここにあるのですか」に答えられません。

現場での姿

最もよく見る場面は、SBOMは作っているのに、誰も2つを見比べていないことです。リリースのたびにファイルが積み上がりますが、前のリリースと今回のリリースの違いを見る人がいません。ところが、サプライチェーン事故の兆候は、ほとんどがその違いにあります。誰も依存を追加していないのに、一覧に新しい名前が1つ増えていることです。誰かが間接依存のバージョン範囲を広く取っていて、新しいバージョンが出たときに、ついて入ってきたのです。

2つ目の場面は、スキャン結果が膨らんで無視されることです。開発専用の依存まで一緒に数えると、数が2倍になり、2倍になった一覧は、開発チームに読まれません。分けて送って初めて読まれます。

3つ目の場面は、一覧に対象が書かれていないことです。componentsは25行がぎっしり詰まっているのに、この一覧がどのアーティファクトのものなのかが、どこにもありません。そうすると、数か月後にそのファイルを開いた人は、「これがあのときのリリースで合っているのか」を確認する方法がありません。対象は名前ではなくダイジェストで書く必要があります。名前は同じ名前で別の内容を指せますが、ハッシュはそうできません。続くモジュールの署名と来歴証明は、すべてこの「対象」の欄の上に載ります。

最後に、一覧をいつ作るかも設計です。ソースを読んで作った一覧と、ビルドのアーティファクトを調べて作った一覧は違います。前者は「何を使うことにしたか」で、後者は「何が実際に出荷されたか」です。2つはたいてい似ていますが、食い違う日が、まさに事故が起きる日です。

次のラボですること

ロックファイル1つから一覧を自分で数えて、CycloneDX形式で書いてみます。誰も選んでいないパッケージがどの経路で入ってきたかを追跡し、前のリリースのSBOMと見比べて、何が増え、減り、変わったかまで数えてみます。ツールが1秒でやることを一度手でやっておくと、あとでツールが出した一覧で、何が抜けているかが見えます。