npmパッケージ/GitHub Actionsを利用する側/公開する側でサプライチェーン攻撃を防ぐためにやることメモ
パッケージを利用する側、パッケージを公開する側でサプライチェーン攻撃を防ぐためにできることのメモ書きです。
パッケージを利用する側
npmやGitHub Actionsなどを利用する側として、サプライチェーン攻撃を防ぐためにできることをまとめます。
ロックファイルを使う
- npmやYarn、pnpmなどのパッケージマネージャーは、依存関係のバージョンを固定するためにロックファイル(例:
package-lock.json
,yarn.lock
,pnpm-lock.yaml
)を使用する
GitHub ActionsではSHA Pinを行う
- pinactなどを使ったGitHub ActionsのSHA Pinを行う
- また、GitHubリポジトリの"Require actions to be pinned to a full-length commit SHA”を有効にする
- renovatebotもdependabotも
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
みたいな書式でアップデートは対応してるので、pre-commitとかにSHA Pinする処理を入れれば気にすることは特になくなる
GitHub Actionsには最小の権限を与える
-
permissions
を設定して、必要な権限だけを与える - 例えば、
contents: read
だけで良い場合は、他の権限を与えない - あまりGitHub Actions側で明確に定義されてないことがあるのでツールは以前書いた
- GitHub Actionsの
permissions
を自動で設定するツールを書いた | Web Scratch - zizmorcore/zizmorやsuzuki-shunsuke/ghalintなどpermissionsの有無はチェックできる
依存のアップデート
サプライチェーン攻撃起きてからすぐパッケージをアップデートしてしまうと受動的に攻撃を受ける可能性がある。
そのため、パッケージが公開されてから1週間経ってからアップデートするPRを作るようなオプションをdependabotやrenovateでは有効にする。
- renovatebot:
minimumReleaseAge
を設定する - dependabot:
cooldown
を設定する
renovatebot/dependabotを装ってlockfileを意図しない形でアップデートする攻撃も考えられる。この場合 dependabotなら手動でマージボタンは押さないで @dependabot merge
コメントをすることで、本物のdependabotかを見分けることができる(権限がない偽物ならマージはできないため)
renovatebotだとこれに対応する方法はないけど、最近はpnpmを使っているのであまり気にしなくなった。この問題をチェックするツールも存在する。
pnpm catalogではlockfileのズレが pnpm install
時にエラーにならないバグがあるけど、workaroundで検出はできる
スキャンを通してないパッケージをnpxで叩かない
- MCP系で特に問題になる
- npxは最新のパッケージを自動でダウンロードして実行してしまうので、パッケージが安全なのかを確認してから実行する必要がある
- 繰り返し実行するなら、そもそも
npx
でダウンロードしないで、devDependencies
に入れておくなどバージョンを固定する -
npx
で叩く場合は、バージョンを指定する e.g.npx some-package@1.2.3
- azu/ni.zshやryoppippi/bun-socket-scannerでは、Socket.devを使ったスキャンを行ってからパッケージを実行できる
AI AgentはSandboxで実行する
- 勝手に外部パッケージを入れたり色々しちゃうため
- サンドボックス環境で実行することで、ホストOSへの影響を最小限に抑える
- 主にたどれるべきではないファイルやネットワークにアクセスされるのを防ぐ
- macOSなら軽量なサンドボックスとして
sandbox-exec
を使う - DevContainersなどDockerコンテナで実行する
パッケージを公開する側
npm registryにパッケージを公開する側として、サプライチェーン攻撃を防ぐためにできることをまとめます。
攻撃者に認証情報を盗まれないようにする方法、アカウントを乗っ取られた場合の被害を軽減する方法などについて
コミットはsecretlintを通す
- secretlintを使って、コミットに秘密情報が含まれていないかをチェックする
- 機密情報はprivate repositoryであっても漏洩する可能性があるので、コミットに含めない
ローカルに生tokenを置かない
- マルウェアなどはローカルのファイルを集めて盗み取ることが多い
- nxの事例ではAI Agentを使ってローカルの認証情報などを集めて盗み取っていた
- https://www.wiz.io/blog/s1ngularity-supply-chain-attack
- そのため、ローカルであっても生の認証情報をファイルに置かない
- Git管理外のファイルもコミットするファイルと同等の意識で管理する
- 認証情報は1Passwordなどのパスワードマネージャーに保存し、
op
コマンドなどで実行時に取得する - 1Passwordを使って、ローカルにファイル(~/.configや.env)として置かれてる生のパスワードなどを削除した | Web Scratch
- Secret Manager的なことをローカルでも行う
- 認証情報は1Passwordなどのパスワードマネージャーに保存し、
TokenのScopeを最小にする
- npmはGranular access tokens
- GitHubはGitHub AppsやFine-grained personal access tokens
- それぞれ最小のスコープを設定したtokenを利用する
- npmはほぼtokenが不要になってきているので、できる限りtokenを発行しないようにする
- GitHubは最小のスコープ(リポジトリと権限)のtokenを発行する
- 広いスコープ(リポジトリ)のtokenは
gh
コマンドぐらいになるイメージで、このtokenも1password連携を使うことでローカルに生のtokenとして存在しない状態にできる
GitHub ActionsはSecurity Checkを行なってからマージする
- GitHub Actionsには典型的なScript Injectionの問題がある
-
- run: echo "${{ github.event.issue.title }}"
のように書いたStepがあると、Issueのタイトルに悪意のあるコードを書かれると、そのコードが実行されてしまう - https://securitylab.github.com/resources/github-actions-untrusted-input/
- そのため、GitHub ActionsのWorkflowを変更するPRは、Security Checkを行なってからマージする
- 自分の場合は、zizmorcore/zizmorをコミット時に通している
- 最近メンテナンスが止まっているがrhysd/actionlintも同様のScript Injectionを検出できる
- suzuki-shunsuke/ghalintも類似するツール
- 背景としては、AdnaneKhan/gato-xのようなツールで、このような脆弱性があるGitHub Actionsは自動的に見つけることができる
npmのTrusted PublishingとOIDC連携を使ってトークンレスでCIからnpmパッケージを公開する
- npm tokenが発行されていること自体がリスクなので、できる限りnpm tokenを発行しないようにする
- CIからのPublishの用途にはOIDC連携を使うことで、npm tokenを発行せずにパッケージを公開できる
- npm Trusted PublishingでOIDCを使ってトークンレスでCIからnpmパッケージを公開する | Web Scratch
npmは"Require two-factor authentication and disallow tokens"を設定する
Require two-factor authentication and disallow tokens
With this option, a maintainer must have two-factor authentication enabled for their account, and they must publish interactively. Maintainers will be required to enter 2FA credentials when they perform the publish. Automation tokens and granular access tokens cannot be used to publish packages.
https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification
- npmのRequire two-factor authentication and disallow tokensをパッケージに設定すると、公開にも2要素認証が必要になるため、npm tokenを使ったパッケージの公開はできなくできる
- publishに使えるnpm tokenがあること自体がリスクなので、npm tokenだけではパッケージを公開できないようにすることで攻撃をしにくくする
- これをデフォルトにしたいという要望を投げている -> Feature Request: Make “Require two-factor authentication and disallow tokens” as default on newly published packages · community · Discussion #172886
MFAはフィッシング耐性の高いものを使う
- SMS/TOTPはフィッシング耐性が低いので、できる限りフィッシング耐性の高いMFAを使う
- WebAuthn/FIDO2などのセキュリティキー/Passkeyを使う
- パスワード管理/MFA管理の戦略 | Web Scratch
- OIDCなどでnpm token自体を減らせたので、自分の場合はnpmにはセキュリティキーのみ(+バックコードを保存)をMFAとして登録している
- GitHubもセキュリティキーのみをMFAにしたいが、昔登録した Authenticator app が消せないというバグがある
Discussion