脆弱性やサプライチェーン攻撃のいたちごっこに疲れていませんか?:Chainguardの特徴とセキュリティ防御力を徹底検証してみた

昨今、セキュリティ界隈では脆弱性やソフトウェアサプライチェーン攻撃に関するニュースが大量に飛び交っています。信頼できるライブラリやコンテナイメージを使用することが大事だとわかっていても、日々の業務に追われ、セキュリティの強化は後回しになってしまい、「弊社のシステムのセキュリティは完璧です」と言い切れる企業は少ないのではないでしょうか。
この記事ではさまざまな攻撃が飛び交う中生まれた一筋の希望の光、もといタコさんであるChainguardを紹介します。
Chainguardとは?
Chainguard社のプロダクトはいくつかありますが、代表的なのは「Chainguard Containers」「Chainguard Libraries」の2つです。ロゴはタコです。
Chainguard Containersとは
セキュリティの強化とソフトウェア・サプライチェーンの整合性を極限まで高めるために設計されたコンテナイメージです。最大の特徴は、一般的なOS層を省いた「ディストロレス(distroless)」の哲学を採用している点にあります。この思想に基づき、通常のコンテナイメージに含まれるシェル、パッケージマネージャ、ユーティリティは除外されています。この設計により潜在的なセキュリティ脆弱性が大幅に軽減されています。
Overview of Chainguard Containers
この特長を持ったイメージを生み出すために、Chainguard社はwolfiという独自のLinux(アン)ディストリビューションを開発しています。
世界最小のタコの名前であるwolfi にインスパイアされてwolfiと名づけられています。
Introducing Wolfi: The first Linux (un)distro designed for securing the software supply chain
https://edu.chainguard.dev/open-source/wolfi/overview
Chainguard Librariesとは
Chainguard Librariesは、強固なコンテナイメージ(Chainguard Containers)を作成するために不可欠な役割を果たしています。このLibrariesで提供されるライブラリは、Sentinel(脅威情報を自動分析するChaiguardの次世代システム) )で脅威チェックや評価を行い、安全だと確認されたのちにChainguard Factoryという専用の環境でビルドされ、顧客に提供されます。これにより、昨今猛威を振るったShai-Huludやaxiosなどのようなパッケージ改ざんを検知し、悪意あるパッケージの混入を事前に排除することが可能です。
さて、私たちはこれらの武器を使って、どうやってソフトウェアサプライチェーン攻撃や脆弱性から身を守ればいいのでしょうか。この記事にたどり着いたみなさんは、きっと相棒の生成AIと一緒に公式ドキュメントを読むでしょうから(公式ドキュメントに「Copy Markdown for LLMs」というボタンもあるぐらいですし!)今回は血の通ったリアルな記事をお届けすべく、手元で動かして実験していきます。
混入経路を知ろう
まずは敵を知るべし。実験を始める前に、ソフトウェアサプライチェーン攻撃や脆弱性はどのように私たちのシステムに混入するのかおさらいしましょう。混入経路はいくつかあります。
経路1:コーディング時にパッケージマネージャ経由で混入する
Shai-huludワームや、axios、LiteLLMへの攻撃がこれにあたります。攻撃者がnpmやPyPIなどのパッケージを改ざんし、悪意あるコードを挿入します。
開発者端末をターゲットにした攻撃と、CI/CDパイプラインをターゲットにした攻撃の2パターンが確認されています。
経路2:CI/CDのビルドシステムが侵害される
Codecov社のセキュリティインシデントがこれにあたります。CodecovはCI/CDパイプライン上で動作し、テストカバレッジデータを生成します。生成の過程で、bash <(curl -s https://codecov.io/bash) を実行してBash Uploaderというスクリプトをインターネット経由でダウンロードし、CI環境の中で実行していましたが、このスクリプトが攻撃者によって改ざんされ、顧客のシークレット情報が窃取される事態へとつながりました。
セキュリティインシデントの詳細については以下の記事もご覧ください。
Codecov社のセキュリティインシデント:CIポイズニング攻撃から得た教訓 #aqua #コンテナ #セキュリティ #CI #サプライチェーン攻撃 - Tech Blog|クリエーションライン
経路3:コンテナレジストリ・配布経路での改ざん
2026年3月 Trivyのサプライチェーン攻撃がこれにあたります。攻撃者はTrivyのGitHub Actionsから認証情報を盗み出し、TrivyのGitHubリポジトリの既存のバージョンタグを改ざんし、さらには、悪意あるコードを注入するTrivy 0.69.4 という偽のバージョンをリリースしました。これにより、いつも通りCI/CDパイプラインでTrivyを実行しただけのつもりが、裏でAPIキーやパスワードなどの認証情報が窃取される事態へとつながりました。
Trivyへのサプライチェーン攻撃については以下の記事もご覧ください。
2026年3月Trivyサプライチェーン攻撃とDockerイメージのバージョンピニング #trivy #docker #security - Tech Blog|クリエーションライン
経路4:悪意あるイメージをうっかり使用してしまう
Docker Hubはサインアップさえすれば誰でもコンテナイメージをアップロードすることができます。裏を返せば、攻撃者が悪意あるコンテナイメージを入れることができるということです。例えば、Aqua SecurityのTeam Nautilusは、Docker Hub上でOpenJDKやGolangの公式イメージに偽装した悪意あるイメージを発見したと報告しています。
Threat Alert: Supply Chain Attacks Using Container Images
まとめると、複数の混入経路があることがわかりました。次は、これらをどうやって防げばいいのか見ていきましょう。

実験
経路1:パッケージマネージャ経由で混入する→Chainguard LibrariesとChainguard Containersでガード!
①開発者端末をターゲットにした攻撃
これはChainguard Librariesが有効です。実際に問題のあるパッケージを用いるのは難しいので、ここではChainguardのリポジトリからnpm installして、SBOMチェックと署名検証までをやってみます。
公式サイトのChainguard Librariesのクイックスタートを見ながら進めていきましょう。
Quick start for Chainguard Libraries
まず、Chainguard Console(Webサイト)にサインアップします。
Organization(組織)を作成しておきます。2026年5月時点では、事前に作っておかないと実際に使用できませんでした。組織は実在のドメインでないと登録できないようですので注意してください。ここではcreationline.comを使用しています。

手元でchainctlコマンドのインストールを行います。今回は実行環境にWSL(Debian)を使用しています。以下のリンクを見ながら進めます。
How to Install chainctl — Chainguard Academy
k-yamamori@WSL:~/chainguard_demo$ mkdir ~/tmp && cd $_
k-yamamori@WSL:~/tmp$ sudo apt install curl
(割愛)
k-yamamori@WSL:~/tmp$ curl -o chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 119M 100 119M 0 0 9534k 0 0:00:12 0:00:12 --:--:-- 10.6M
k-yamamori@WSL:~/tmp$ ls
chainctl
k-yamamori@WSL:~/tmp$ ls -la
total 122404
drwxr-xr-x 2 kayou kayou 4096 May 28 13:52 .
drwx------ 10 kayou kayou 4096 May 28 13:51 ..
-rw-r--r-- 1 kayou kayou 125329570 May 28 13:52 chainctl
JavaScriptのエコシステムを有効にします。ブログ掲載のため、organizationUIDは実際の出力をマスクしています。
k-yamamori@WSL:~$ chainctl auth login Opening browser to https://issuer.enforce.dev/oauth?audience=https%3A%2F%2Fconsole-api.enforce.dev&client_id=auth0&connection=google-oauth2&create_refresh_token=true&exit=redirect&redirect=http%3A%2F%2Flocalhost%3A45971%2Fcallback%3Ftoken%3Dtrue%26error%3Dtrue&skip_registration=true Successfully exchanged token. Valid! Id: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
npmのセットアップをして、パッケージを1つ入れてみます。
k-yamamori@WSL:~$ mkdir cg-npm-demo && cd cg-npm-demo
k-yamamori@WSL:~/cg-npm-demo$ npm init -y
Wrote to /home/kayou/cg-npm-demo/package.json:
{
"name": "cg-npm-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
chainctl auth configure-npmを実行すると.npmrcを自動的に生成してくれます。
k-yamamori@WSL:~/cg-npm-demo$ chainctl auth configure-npm Opening browser to https://issuer.enforce.dev/oauth?audience=libraries.cgr.dev&client_id=auth0&connection=google-oauth2&create_refresh_token=true&exit=redirect&redirect=http%3A%2F%2Flocalhost%3A40959%2Fcallback%3Ftoken%3Dtrue%26error%3Dtrue&skip_registration=true Configured npm for Chainguard Libraries for JavaScript. Written to .npmrc
chainguardのリポジトリにつながるか確認します。
k-yamamori@WSL:~/cg-npm-demo$ npm ping --userconfig .npmrc npm notice PING https://libraries.cgr.dev/javascript/ npm notice PONG 313ms
公式ドキュメントに例として上がっていたcommander@4.1.1をインストールします。
k-yamamori@WSL:~/cg-npm-demo$ npm add commander@4.1.1 added 1 package, and audited 32 packages in 2s found 0 vulnerabilities k-yamamori@WSL:~/cg-npm-demo$ npm install up to date, audited 32 packages in 478ms found 0 vulnerabilities k-yamamori@WSL:~/cg-npm-demo$ npm list cg-npm-demo@1.0.0 /home/kayou/cg-npm-demo ├── axios@1.16.1 └── commander@4.1.1
今回使用するコマンド(chainctl libraries verify)はtgzファイルを対象に行うため、npm pack でtgzにします。
k-yamamori@WSL:~/cg-npm-demo$ npm pack commander@4.1.1 npm notice npm notice 📦 commander@4.1.1 npm notice === Tarball Contents === npm notice 1.1kB LICENSE npm notice 23.5kB Readme.md npm notice 43.1kB index.js npm notice 929B package.json npm notice 9.3kB typings/index.d.ts npm notice === Tarball Details === npm notice name: commander npm notice version: 4.1.1 npm notice filename: commander-4.1.1.tgz npm notice package size: 22.0 kB npm notice unpacked size: 78.0 kB npm notice shasum: 7a41be06f86a49f44ac29b710de4843c5731a27f npm notice integrity: sha512-bzlYHj4wDBX2f[...]5Izfc+WRi57bg== npm notice total files: 5 npm notice commander-4.1.1.tgz
うっかりしていました、検証にはcosignが必要でした。同じタイミングでインストールしておきます。
cosignのアスキーアートが出ればバッチリです。
How to Install Cosign — Chainguard Academy
k-yamamori@WSL:~/cg-npm-demo$ which cosign /usr/bin/cosign k-yamamori@WSL:~/cg-npm-demo$ cosign version ______ ______ _______. __ _______ .__ __. / | / __ \ / || | / _____|| \ | | | ,----'| | | | | (----`| | | | __ | \| | | | | | | | \ \ | | | | |_ | | . ` | | `----.| `--' | .----) | | | | |__| | | |\ | \______| \______/ |_______/ |__| \______| |__| \__| cosign: A tool for Container Signing, Verification and Storage in an OCI registry. GitVersion: v3.0.6 GitCommit: f1ad3ee952313be5d74a49d67ba0aa8d0d5e351f GitTreeState: clean BuildDate: 2026-04-06T21:39:58Z GoVersion: go1.25.7 Compiler: gc Platform: linux/amd64
それでは検証してみましょう。chainctl libraries verifyコマンド一発でできます。
k-yamamori@WSL:~/cg-npm-demo$ chainctl libraries verify commander-4.1.1.tgz One organization available, selecting creationline.com (id: xxxx=organizationUID=xxx) Artifact: commander-4.1.1.tgz Verification Coverage: 100.00%
Verification Coverage: 100.00%、つまりこのパッケージは100%chainguardによってビルドされたものだとはっきり分かりました!
同様の手順でaxiosも検証できました。
k-yamamori@WSL:~/cg-npm-demo$ chainctl libraries verify axios-1.16.1.tgz One organization available, selecting creationline.com (id: xxxx=organizationUID=xxx) Artifact: axios-1.16.1.tgz Verification Coverage: 100.00%
chainctl libraries verifyを使ったパッケージの検証については、以下のドキュメントも参照してください。
Chainguard Libraries verification
このように、Chainguard Librariesが提供するパッケージはすべてビルド時に厳格に評価され、改ざん防止の電子署名が施されています。開発者が chainctl libraries verify コマンドを用いることで、「今からインストールしようとしているパッケージが、第三者に改ざんされていない本物(クリーン)であること」をローカル環境で100%立証できるため、悪意あるコードの混入を水際で完全にシャットアウトできるのです。
②CI/CDパイプラインをターゲットにした攻撃
こちらはChainguard Containersが有効です。
実際にshai-huludワームを用いるのは難しいので、今回はnpm install時に実行されるJavascript内でシェルを呼び出して、Docker Hubのnode:latestと、Chainguardのnode:latestでどのような挙動をするかをデモで比較してみます。
使用するコマンドは以下の通りです。
docker run --rm node:latest node -e "try { require('child_process').execSync('echo \"裏でシェル(sh)が起動しちゃった!奪われた権限:\" && id', {stdio: 'inherit'}); } catch(e) { console.error(e); }"
まず、Docker Hubのnode:latest で実行してみます。rootを取られてしまいました。
k-yamamori@WSL:~$ sudo docker run --rm node:latest node -e "try { require('child_process').execSync('echo \"裏で
シェル(sh)が起動しちゃった!奪われた権限:\" && id', {stdio: 'inherit'}); } catch(e) { console.error(e); }"
Unable to find image 'node:latest' locally
latest: Pulling from library/node
8a7504cd2818: Pull complete
b53089dca505: Pull complete
326172fd43b9: Pull complete
9bc05f2fa337: Pull complete
f32f49ce655a: Pull complete
8d6d44b254da: Pull complete
bacec82ae09b: Pull complete
22d60d34c92f: Download complete
3bdf747f8c7a: Download complete
Digest: sha256:980c5420a7a2ddcb44037726977f2a349e5c7b64217516c7488dce4c74d71583
Status: Downloaded newer image for node:latest
裏でシェル(sh)が起動しちゃった!奪われた権限:
uid=0(root) gid=0(root) groups=0(root)
次に、Chainguard Containersのnode:latestで実行してみます。
javascriptからシェルが動かすところまでは成功しましたが、rootは取られませんでした。
k-yamamori@WSL:~$ sudo docker run --rm cgr.dev/chainguard/node:latest -e "try { require('child_process').execSync('echo \"裏でシェル(sh)が起動しちゃった!奪われた権限:\" && id', {stdio: 'inherit'}); } catch(e) { console.error(e); }"
Unable to find image 'cgr.dev/chainguard/node:latest' locally
latest: Pulling from chainguard/node
4e7d21dff9f4: Pull complete
d8bc09c55230: Pull complete
29ec2435dd7f: Pull complete
7677d1f93ece: Pull complete
a23c4200287e: Pull complete
0e6fa87de9d9: Pull complete
0dff3cedfc4b: Pull complete
72af4232fa8b: Pull complete
a5bfa7101982: Pull complete
07734805cb32: Pull complete
9b0df80e6dc5: Pull complete
Digest: sha256:045335a479d6c59bab89e3caaaf9ed2aed5528d92e2431a3e2afcbf258dba9a6
Status: Downloaded newer image for cgr.dev/chainguard/node:latest
裏でシェル(sh)が起動しちゃった!奪われた権限:
uid=65532(node) gid=65532(node) groups=65532(node)
これは、Chainguardのコンテナが非rootユーザーでの実行をデフォルトにしているためです。
How to Port a Sample Application to Chainguard Containers 内にも「Chainguard Containers typically don’t run as root, so a USER root statement may be required before installing software. This should be a temporary escalation only; after completing any root-level operations, you should create and switch to a dedicated non-root user (for example, using addgroup and adduser) or use the image’s built-in non-root user. Leaving the container running as root defeats the security purpose of using minimal images.」
日本語に訳すと「Chainguardコンテナは通常root権限で実行されないため、ソフトウェアのインストール前にUSER rootステートメントが必要になる場合があります。これは一時的な権限昇格に過ぎません。rootレベルの操作が完了したら、専用の非rootユーザーを作成して切り替えるか(例えば、addgroupとadduserを使用)、イメージに組み込まれている非rootユーザーを使用してください。コンテナをroot権限で実行したままにしておくと、最小限のイメージを使用するセキュリティ上の目的が損なわれます。」という記載があります。
今回使用したnode:latestイメージのスペックシートにも、今回取られたユーザIDである65532の記載があることが分かります。
デモの結果と公式ドキュメントが示している通り、Docker Hubの標準イメージでは悪意あるスクリプトによって簡単にホストまで脅かす特権(root)を奪われてしまいます。
一方、Chainguard Containersは、スペックシートにある通り最初から UID: 65532 (non-root) での実行が徹底されているため、万が一コンテナ内でシェルを起動されるような侵害を受けても、構造的に特権奪取(root)を防ぎ、被害を最小限に食い止めることができるのです。
経路2:CI/CDのビルドシステムが侵害される→SASTツールを組み合わせてガード
2021年のCodecovインシデントに見られるように、「業務上、bashや外部通信が必要なステップ」では、コンテナイメージの最小化(静的防御)だけでは限界があります。パイプライン内から呼び出される外部スクリプトが汚染されている場合、正規の処理として通信が実行されてしまうからです。
これに対するアプローチはいろいろありますが、スクリプトの整合性のチェックをすることが有効です。署名のチェックをパイプラインに実装することで、不正に変更されたスクリプトを実行しないようにします。他の有効な手段としてSemgrepなどのSASTツールを用いた「外部スクリプトの静的解析」があげられます。
経路3:コンテナレジストリ・配布経路での改ざん→Chainguard Containersでガード
2026年3月のTrivyへの攻撃では、GitHubで管理されているActions(uses: aquasecurity/trivy-action)のタグ自体が書き換えられてしまいました。結局のところ、公式のActionsの仕組みをそのまま使っている限り、配布元(GitHub)が侵害されたらユーザー側では防ぎようがありません。バージョン固定(ピニング)すら無力化されてしまいます。
そこで有効なのが、Actionsを使うのをやめて、Chainguardが提供するTrivyのコンテナイメージを直接指定して実行する(docker run cgr.dev/chainguard/trivy など)運用に切り替えることです。
Chainguard ContainersにあるTrivyイメージは、汚染されたタグやバイナリを一切通さず、ソースコードから直接クリーンな環境でビルドして提供しているため、今回のサプライチェーン攻撃の影響を全く受けませんでした。Actions依存のリスクを回避し、安全にスキャンを実行するための強力な選択肢となります。
参考: Trivy supply chain attack: What security teams need to know
経路4:悪意あるイメージをうっかり使用してしまう→Chainguard Containersでガード
悪意あるイメージをうっかり使用してしまうリスクは、Chainguard Containersの導入により、コンテナ調達先を100%検証済みの安全なレジストリ「cgr.dev」に一本化することで極力減らすことができます。このアプローチは、ビルド時に自動生成されるSBOMを通じて不透明なサードパーティ製イメージの誤混入を完全にシャットアウトします。
さいごに
もちろん「Chainguardを導入すれば100%すべて安心!」というわけではありません。しかし、導入によって運用の工数やリソースに大きな「余裕」が生まれることは間違いありません。
その生まれた余裕を使って、パッケージのクールダウン期間の導入や、npmでインストールスクリプトをデフォルトで実行させない(ignore-scriptsなど)といった泥臭くも本質的な対策・社内啓蒙、そして日々のセキュリティニュースのキャッチアップに時間を投資していく。これこそが、システムを真に堅牢にしていくためのアプローチだと考えています。
今回の検証では、想定されるサプライチェーンの混入経路を可能な限り列挙したつもりですが、「いやいや、こういう経路でも混入するよ!」というナレッジがありましたらお知らせください。マサカリは大歓迎です!
Chainguardについては、他にもまだまだ気になる検証テーマが山ほどあるので、検証ができ次第、また発信していきたいと思います。
ここまで読んでいただき、ありがとうございました!「うちの環境のサプライチェーンセキュリティも相談したいな…」と少しでも気になった方は、弊社にお問い合わせください。
蛇足:アイキャッチは、wolfiたちが一生懸命マルウェアを探している様子を描いてみました。
