sourcemapのsourcesContentは、どのcommitのsourceか特定できるか。Git historyと照合するsourcemap-lineage

npmで公開されているJSファイルには、しばしば .js.map が付属している。中身を見ると sourcesContent というフィールドに、ビルド前のsource全文がそのまま入っていることがある。これを見て、「このartifactが、手元にcloneしたGit historyのどのあたりから来たのか分かるのではないか」と思ったのが出発点だった。

目次

sourcesContentだけでは、commitは分からない

sourcesContent は「ビルド時にそのsource fileがどんな中身だったか」を教えてくれる。しかしそれだけでは「どのcommitのsourceか」までは分からない。同じ内容のfileが複数のcommitに存在していれば、sourcesContent単体ではそのどれか一つに絞り込む手段がない。

これを補うために作ったのが sourcemap-lineage という、read-onlyのCLI toolになる。

Git historyと照合する

sourcemap-lineageがやっていることは単純で、artifactのsourcemapから sourcesContent を取り出し、手元にcloneしたGit repositoryの各commitの該当fileと比較するだけ。使っているgit操作も git loggit ls-treegit cat-file のみで、履歴の中身を読むだけの操作に限られる。

node sourcemap-lineage.cjs dist/app.js --repo /path/to/repo

recoverしたsourceの内容が、あるcommit時点で追跡されているfileのblobと一致すれば、そのcommitは「候補」になる。一致しなければ、そのcommitは候補から外れる。

同じsourceのcommitは区別できない

ここで問題になるのが、対象fileの中身が変わっていないcommitが複数存在する場合だ。たとえば「READMEだけ直したcommit」は、src/以下のfileの中身をまったく変えていない。sourcemap-lineageが見ているのは追跡対象fileのblobの一致なので、この2つのcommitは区別がつかない。

ここで、無理に「どちらか一つ」を選ばないようにした。sourcemap-lineageは、blobが一致するcommitをまとめて一つの候補groupとして報告する。1個のcommitに絞り込めたように見せかけるのではなく、「区別できない」という事実をそのまま出力に残す設計にしている。

検証済みの実例: Immer 10.1.1

npmで公開されている immer@10.1.1dist/immer.mjs を対象に、公開情報だけで再現できる形で検証した。

  • artifact: dist/immer.mjs(SHA-256: 251bf957...2fc49a
  • npm registryのgitHeadメタデータ(今回の照合対象の正解として使うが、tool自体には渡していない): e2d222bd4fb26abded04075c936290715e9ee335
  • 公開GitHub repository: immerjs/immer

sourcemap-lineageを実行すると、12個のsourceすべてがGit historyの特定のblobと完全一致した。

2 commitのgroupまで絞れた

結果は次の通りだった。

  • sourceGit: EXACT(12/12 exact match)
  • 候補commitは2個のgroup(gitHeadに記録されていた正解commitは、そのgroupに含まれていた)
  • Classification: AMBIGUOUS / Evidence level: STRONG_CANDIDATE
  • dirtyBuild: NOT_INDICATED

「12個全部のsourceが完全一致」という強い証拠があっても、それだけでcommitを1個に確定させることはできない。この2 commitはsrc/以下のtracked fileが完全に同一で、artifactの側から見分ける手段がないからだ。

しかしprovenance proofではない

ここが一番大事な点になる。sourcemap-lineageが示しているのは「recoverしたsourceの内容が、Git historyのこのあたりのcommitと一致する」という事実だけであって、「このartifactが実際にどのmachine・どのbuild環境で作られたか」を証明するものではない。SLSAのようなbuild provenance、tamper-proofなattestation、exact single-commitの断定とは別物であり、実行結果には毎回 Artifact-level attribution: NOT_ESTABLISHED と明示している。

Immerのケースでも、AMBIGUOUSSTRONG_CANDIDATE が同時に出ているのは矛盾ではない。「source側の一致は強いが、それだけでは1つのcommitに決められない」という状態をそのまま表している。

Reduxでは何も分からないままだった

もう一つ、逆のケースも記録しておきたい。redux@5.0.1dist/redux.browser.mjs を同じ手順で調べたところ、sourcemapに含まれる10個のsourceのうち、公開repositoryのどのcommitのblobとも一致しなかった。

この場合、sourcemap-lineageは無理に「一番近そうなcommit」を候補として出すことをしない。Classification: UNKNOWN として、候補commitもsource差分も一切表示せずに終わる。中途半端な一致から誤った候補を出すより、何も分からない状態を「分からない」とそのまま返す方を選んでいる。

両方のscriptは、公開されているnpm packageとGitHub repositoryだけで再現できる(real-cases/ 以下)。

GitHub

github.com/iwadjp/sourcemap-lineage

これまで作った他のtoolについては、こちらにまとめている。

AI coding / debugging tools まとめ