## 経緯
昨今は[[npmレジストリ]]に対する数々の攻撃が激化しており、自己防衛する必要があるため。
## スコープ
開発者として最低限必要なことをやっておく。ライブラリ開発者のアクションはスコープ外とする。(パッと見はやれてそうだし)
以下のリポジトリの内容を参考に進める。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
GitHub - bodadotsh/npm-security-best-practices: How to stay safe from NPM supply chain attacks
</div>
<div class="link-card-v2-content">
How to stay safe from NPM supply chain attacks. Contribute to bodadotsh/npm-security-best-practices development ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/5c06c0762731d936cb4e3f089a6310518e1801910f234f41b2e73d8b27f0ce51/bodadotsh/npm-security-best-practices" />
<a href="https://github.com/bodadotsh/npm-security-best-practices"></a>
</div>
対象[[パッケージマネージャー]]は普段使っている以下4つとする。
- [[npm]]
- [[pnpm]]
- [[Bun]]
- [[Deno]]
| 対象 | バージョン |
| -------- | ------- |
| [[npm]] | 11.6.1 |
| [[pnpm]] | 10.22.0 |
| [[Bun]] | 1.3.2 |
| [[Deno]] | 2.5.1 |
## 1. 依存関係のバージョンをピン留めする
依存関係のバージョンを正確な値にする設定。[[セマンティックバージョニング]]の幅をもたせる表現はしないことで、マイナーバージョンアップやパッチバージョンアップによる不正なライブラリバージョンのインストールリスクを軽減できる。
### 設定方法
#### [[npm]]
```console
npm config set save-exact=true
```
`~/.npmrc` に設定が追加される。
#### [[pnpm]]
[[npm]]の設定がされていれば不要。
> [!info] [[npm]]の設定をせずに設定したい場合
>
> ```console
> pnpm config set save-exact true
> ```
>
> `~/Library/Preferences/pnpm/rc` に設定が追加され `--save-exact` なしでもバージョンが固定される。
#### [[Bun]]
[[npm]]の設定がされていれば不要。
> [!info] [[npm]]の設定をせずに設定したい場合
> `bunfig.toml` に以下を追加。
>
> ```toml
> [install]
> exact = true
> ```
#### [[Deno]]
設定は存在しない。コマンドでexact versionを指定する。([[#コマンドで指定する場合]] を参照)
### コマンドで指定する場合
毎回指定する必要があるので設定の方を推奨。
```console
npm install --save-exact <package>
pnpm add --save-exact <pacakge>
bun add --exact <pacakge>
deno add npm:
[email protected]
```
### Override the transitive dependencies (必要なときだけ)
依存しているライブラリがさらに依存するバージョンを固定する方法。
#### [[npm]]
`package.json` に `overrides` を追加。
```json
{
"dependencies": {
"owlelia": "0.49.0"
},
"overrides": {
"dayjs": "1.11.8"
}
}
```
これで、依存関係がさらに依存する[[dayjs]]は `1.11.8` に固定される。
#### [[pnpm]]
[[pnpm-workspace.yaml]] に `overrides` を追加。
```yaml
overrides:
"dayjs": "1.11.8"
```
これで、依存関係がさらに依存する[[dayjs]]は `1.11.8` に固定される。
#### [[Bun]]
[[npm]]と同様。
> [Overrides and resolutions - Bun](https://bun.com/docs/install/overrides)
#### [[Deno]]
#2025/11/26 時点ではまだサポートされてなさそう。
> [Support npm overrides · Issue #28664 · denoland/deno](https://github.com/denoland/deno/issues/28664)
## 2. ロックファイルを管理してインストールに利用する
[[package.json]]などの依存関係記述ファイルと、[[package-lock.json]]などのロックファイルに齟齬がある場合はエラーになるインストール方法。パッケージバージョン指定がちゃんとできていなかったことによる、危険なバージョンのライブラリインストールリスクを軽減できる。
```console
npm ci
pnpm install --frozen-lockfile
bun install --frozen-lockfile
deno install --frozen
```
通常のインストールとの違いは以下。
> [[npm installとnpm ciの違い]]
これは[[npm]]の話だが、[[pnpm]]、[[Bun]]、[[Deno]]も同様である。
## 3. ライフサイクルスクリプトを無効にする
`pre*` `post*` のような[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]を無効化する方法。インストール時にローカルマシンへ攻撃されるリスクを軽減できる。
### 設定 or コマンド実行方法
#### [[npm]]
以下の設定で、ルートプロジェクトと依存関係含めたすべての[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]を無効化できる。
```console
npm config set ignore-scripts true
```
#### [[pnpm]]
以下の設定で、ルートプロジェクトと依存関係含めたすべての[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]を無効化できる。
```console
pnpm config set ignore-scripts true
```
#### [[Bun]]
[[--ignore-scripts (Bun)|--ignore-scripts]] をつけてインストールすると、ルートプロジェクトと依存関係含めたすべての[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]を無効化できる。設定はできなそう。
```console
bun install --ignore-scripts
```
#### [[Deno]]
[[package.json]]ではなく `deno.json` の用途がメインなので割愛する。
### 注意点
[[npm]]以外はデフォルト無効と書かれているが、これはややラフな説明であるため補足する。
> For `bun`, `deno` and `pnpm`, they are disabled by default.
>
> *[GitHub - bodadotsh/npm-security-best-practices: How to stay safe from NPM supply chain attacks](https://github.com/bodadotsh/npm-security-best-practices?tab=readme-ov-file#3-disable-lifecycle-scripts) *
- [[npm]]
- デフォルトでは有効だが **[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]実行時の[[標準出力]]は表示されない** ので実行されていることが分かりにくい
- [[標準エラー出力]]には表示される
- [[npm]]以外
- 無効になるのは **依存関係の[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]** に限る
- ルートプロジェクトの[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]は無効にならない
- [[pnpm]]
- **バージョンが10未満の場合は**デフォルトで無効になっていない
- [[Bun]]
- **[上位500パッケージ](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt) の[[ライフサイクルスクリプト (npm)|ライフサイクルスクリプト]]は無条件に実行されてしまう**
- これらが[[サプライチェーン攻撃]]を受けた場合はリスクである
[[Deep Research (Gemini)|Deep Research]]の結果に詳しく書かれているので、そちらも参考に。(全部あっているとは限らないので注意)
> [!left-bubble] ![[gemini-mini-chara.webp]]
> - [# 現代のJavaScriptパッケージマネージャーにおけるライフサイクルスクリプトのセキュリティ比較:pnpm、Bun、Denoのデフォルト挙動の妥当性に関する包括的調査報告書](https://gemini.google.com/share/6bfc7e8336f7)
> - [Node.jsエコシステムにおけるライフサイクルスクリプトのセキュリティモデルと実行ポリシーに関する包括的調査報告書
](https://gemini.google.com/share/fada78ebad29)
## 4. Minimal Release Ageの指定
新しいパッケージバージョンが更新されてから、インストールできるようになるまでのラグを意図的に設定する。[[サプライチェーン攻撃]]によって不正化したライブラリがインストールされるリスクを軽減できる。(数日経てば対策がとられることも多いため)
- 動作は未確認
- 特定ライブラリだけすぐにアップデートしたい場合はexclude系の設定を利用する
### [[npm]]
```bash
# macOSの場合. 1週間ラグを設ける
npm install --before="$(date -v -7d)"
```
### [[pnpm]]
[v10.16](https://pnpm.io/blog/releases/10.16) で対応された。
```bash
# 1週間ラグを設ける(分設定)
pnpm config set minimumReleaseAge 10080
```
プロジェクト単位でも設定できる。
```console
pnpm config set --location=project minimumReleaseAge 10080
```
> [Settings (pnpm-workspace.yaml) > minimumReleaseAge | pnpm](https://pnpm.io/settings#minimumreleaseage)
### [[Bun]]
[v1.3](https://bun.com/blog/bun-v1.3#minimum-release-age) で対応された。
`bunfig.toml`
```toml
[install]
minimumReleaseAge = 604800 # 1週間ラグを設ける(秒設定)
```
> [bun install > Minimum release age - Bun](https://bun.com/docs/pm/cli/install#minimum-release-age)
### [[Deno]]
[v2.5.5](https://github.com/denoland/deno/releases/tag/v2.5.5) で対応された[[minimumDependencyAge (Deno)|minimumDependencyAge]]を使う。
`deno.json`
```json
{
// 1週間ラグを設ける(分設定)
"minimumDependencyAge": 10080
}
```
> [feat(unstable): ability to specify minimum dependency age in deno.json file by dsherret · Pull Request #31007 · denoland/deno](https://github.com/denoland/deno/pull/31007)