simple-git: `VISUAL` エディタ環境変数が、安全でないエディタの検出において除外されています。

# `@simple-git/argv-parser` におけるセキュリティ制御の回避: Git の `VISUAL` エディタの代替手段が、`allowUnsafeEditor` として分類されていません。

## レポートのメタデータ

| 項目 | 値 |
| --- | --- |
| パッケージ | `@simple-git/argv-parser` (npm, `pkg:npm/%40simple-git/argv-parser`) |
| リポジトリ | https://github.com/steveukx/git-js |
| コンポーネント | `parseEnv` (`packages/argv-parser/src/env/parse-env.ts`)。`vulnerabilityCheck(tokens, env)` を経由して到達します。 |
| 脆弱性の種類 | セキュリティ制御の回避 — 安全でないエディタの検出における不完全な許可リスト |
| 検証対象 | `c427fbad33f1f2b11341f1cf852eedecbb106400` (`@simple-git/argv-parser` 1.1.1) および公開されている npm アーティファクト 1.1.1。`main` ブランチの `98864c6` で、依然として修正が適用されていません。 |
| API 表面 | 公開されているドキュメント化された API (`parseEnv(raw)` / `vulnerabilityCheck(tokens, env)`) |
| デフォルト設定で影響を受けるか | はい — デフォルトオプションで `blockUnsafeOperationsPlugin` を使用した場合に再現され、安全でない操作の許可が有効になっていません。 |

## 概要

`packages/argv-parser/src/env/parse-env.ts` の `GitEnvKeys` は、`editor`、`git_editor`、および `git_sequence_editor` のみ `allowUnsafeEditor` カテゴリにマッピングされています。`prepareEnv` は、環境変数の名前が小文字に変換されたものが既知の `GitEnvKey` であるか、または `git` で始まる場合にのみ、そのエントリを保持するため、`VISUAL` は `collectConfigVulnerabilities` がそれを検査する前に破棄されます。しかし、Git はエディタを解決する際に `VISUAL` を代替手段として使用するため、`parseEnv({ VISUAL: '/tmp/evileditor' })` は脆弱性を報告しませんが、インタラクティブな Git 操作ではそのバイナリが実行されます。

アプリケーションが環境変数を参照する場合、リクエストやジョブから取得された環境変数は、子Git環境に渡され、このパーサーによって分類されます。このパーサーは、特定の種類の環境変数を分類するために存在し、`EDITOR`や`GIT_EDITOR`といった同等の変数は拒否されます。これにより、攻撃者がエディタを置き換えることができなくなります。この機能は、特にそのような攻撃を防ぐために設計されています。

- `EDITOR` -> `allowUnsafeEditor`として分類されます (`GitEnvKeys`, 5行目-27行目)
- `GIT_EDITOR` -> `allowUnsafeEditor`として分類されます (`GitEnvKeys`, 5行目-27行目)
- `GIT_SEQUENCE_EDITOR` -> `allowUnsafeEditor`として分類されます (`GitEnvKeys`, 5行目-27行目)
- `VISUAL` -> `GitEnvKeys`に存在しません。`prepareEnv`によって削除されます (60行目-68行目)。**脆弱性は報告されません。**

## 影響

攻撃者が操作できる環境変数の設定が許可されているアプリケーションでは、攻撃者が選択した実行ファイルが、`git commit --amend`などの操作中にGitによって実行される可能性があります。これにより、パーサーによるデフォルトの安全でないエディタの保護を回避できます。実行は、Gitの子プロセスを実行しているホストユーザーの権限で行われ、攻撃者が提供したバイナリが、リポジトリのエディタファイル(例えば`.git/COMMIT_EDITMSG`や、`git rebase -i`の場合`.git/rebase-merge/git-rebase-todo`)に対して実行されます。

新しい機能は、この回避機能そのものです。この脆弱性がない場合、`EDITOR`、`GIT_EDITOR`、または`GIT_SEQUENCE_EDITOR`で提供された攻撃者によって制御された値は、アプリケーションが明示的に`allowUnsafeEditor`を有効にしない限り拒否されます。`VISUAL`の場合、同等のコード実行は、明示的な設定や脆弱性の報告なしに実行されます。`VISUAL`は、パーサーが警告する変数である`EDITOR`よりも優先されます。

評価に関する注意点:この脆弱性に関するCVSSベクトルまたはスコアは利用できません。また、ここではそれらを特定していません。脆弱性の影響は、利用するアプリケーションのデータフローに依存します。具体的には、攻撃者が操作した環境変数が、Gitのサブプロセス環境に渡されるかどうかです。信頼できない環境変数をGitに一切渡さない、または常に優先度の高い`GIT_EDITOR`または`core.editor`を設定しているアプリケーションは、影響を受けません。

## 前提条件

1. 利用するアプリケーションが、攻撃者が操作した環境変数を、Gitのサブプロセスに渡す環境変数として使用します(例:simple-gitの`.env()`)。そして、サブプロセスを起動する前に、これらの環境変数をこのパーサーで処理します。
2. Gitコマンドがエディタを開きます(例:`-m`オプションなしの`commit`、`commit --amend`、または`rebase -i`)。
3. `VISUAL`環境変数を上書きするような、より優先度の高いエディタ設定が存在しません。`GIT_EDITOR`、`core.editor`、および`EDITOR`環境変数が設定されていない状態です(`GIT_EDITOR`と`core.editor`は`VISUAL`よりも優先されます。`VISUAL`自体が`EDITOR`を上書きします)。
4. `TERM`環境変数が、正確に`dumb`という値以外に設定されています。Gitは、この場合にのみ`VISUAL`環境変数を参照します。`TERM`は、`GitEnvKey`でも、`git`で始まる名前でもありません。そのため、攻撃者が環境変数を制御している場合でも、このパーサーは何も報告しません。
5. 攻撃者が選択した実行ファイルが存在し、ホスト上で実行可能です。

この脆弱性の発生には、特殊な設定や、通常とは異なる使用方法、または異常な構成は必要ありません。影響を受ける箇所は、ドキュメントに記載されている、デフォルトで有効になっている保護機能です。`docs/PLUGIN-UNSAFE-ACTIONS.md`(「テキストエディタ」)では、この保護機能が、任意のバイナリを置換するエディタ環境変数に対応していると説明されていますが、`EDITOR`、`GIT_EDITOR`、および`GIT_SEQUENCE_EDITOR`のみがリストアップされています。Gitの`VISUAL`へのフォールバックについては言及されておらず、検証されたコミット時点では、リポジトリ全体で文字列`visual`は使用されていません。ドキュメントには、環境変数の設定は呼び出し元の責任であると記載されていますが、`VISUAL`が保護の対象外であるという警告はありません。

フィードペイロードの前提条件リストには、別の `GIT_CONFIG_PARAMETERS` / `allowUnsafeConfigEnvCount` の設定インジェクションのシナリオに関連する項目も含まれています。しかし、ここではそれらは必要ありませんでした。この回避策は、デフォルトのオプションを使用し、安全でない設定を許可しない状態で再現されました。

## データフロー

1. **起点 (Source)**:攻撃者が操作した `raw` 環境オブジェクトが、パブリックのパーサーのエントリーポイントである `parseEnv(raw)` に入力されます ([`packages/argv-parser/src/env/parse-env.ts:70`](https://github.com/steveukx/git-js/blob/c427fbad33f1f2b11341f1cf852eedecbb106400/packages/argv-parser/src/env/parse-env.ts#L70))。
2. **伝播 (Propagation)**:`prepareEnv` はキーを小文字に変換し、既知の `GitEnvKeys` または `git` で始まる名前を持つものだけを保持します。`visual` はどちらでもないため、このエントリは分析が行われる前に破棄されます ([`packages/argv-parser/src/env/parse-env.ts:60-68`](https://github.com/steveukx/git-js/blob/c427fbad33f1f2b11341f1cf852eedecbb106400/packages/argv-parser/src/env/parse-env.ts#L60-L68))。
3. **終点 (Sink)**:したがって、`collectConfigVulnerabilities` は `VISUAL` に対して `allowUnsafeEditor` を決して発行せず、`vulnerabilityCheck(tokens, env)` は空のリストを返します ([`packages/argv-parser/src/env/parse-env.ts:45-54`](https://github.com/steveukx/git-js/blob/c427fbad33f1f2b11341f1cf852eedecbb106400/packages/argv-parser/src/env/parse-env.ts#L45-L54))。
4. **到達可能性 (Reachability)**:simple-git の `blockUnsafeOperationsPlugin` は、プロセスを起動する前に、その環境を `vulnerabilityCheck` に渡します。空の脆弱性リストは、Git の子プロセスが起動することを許可します ([`simple-git/src/lib/plugins/block-unsafe-operations-plugin.ts:12-20`](https://github.com/steveukx/git-js/blob/c427fbad33f1f2b11341f1cf852eedecbb106400/simple-git/src/lib/plugins/block-unsafe-operations-plugin.ts#L12-L20))。

## 脆弱なコード

`packages/argv-parser/src/env/parse-env.ts` の 5 行目から 27 行目 (コミット `c427fbad33f1f2b11341f1cf852eedecbb106400`)。

```ts
const GitEnvKeys = {
'editor': 'allowUnsafeEditor',
// ...
'git_editor': 'allowUnsafeEditor',
// ...
'git_sequence_editor': 'allowUnsafeEditor',
// VISUAL が存在しない
} as const satisfies Record<string, VulnerabilityCategory>;
```

ここにマッピングされている3つのキーは、安全な設定項目です。 欠けている `visual` の項目が、問題点です。 `prepareEnv` 関数(60行目から68行目)は、このマッピングと `git` というプレフィックスに基づいてフィルタリングを行うため、`visual` が欠けているという問題は、単に分類が不足しているというだけでなく、その値がそもそも分類器に到達しないということを意味します。

## 再現

**検証済み** — `c427fbad33f1f2b11341f1cf852eedecbb106400` のチェックアウト(`packages/argv-parser/package.json` = 1.1.1)および公開された npm アーティファクト 1.1.1 に対して、動的に、エンドツーエンドで再現されました。

以下の状況で確認されました(リポジトリに対する vitest の PoC 実行):

- `parseEnv({ EDITOR })`, `parseEnv({ GIT_EDITOR })` および `parseEnv({ GIT_SEQUENCE_EDITOR })` は、それぞれ `allowUnsafeEditor` という脆弱性を1つずつ検出します。
- `parseEnv({ VISUAL: '/tmp/poc/evileditor' })` は、どのような場合でも `[]` を返します。
- `vulnerabilityCheck(['commit', '--amend'], { VISUAL })` — spawn guard が行う正確な呼び出し — は `[]` を返します。
- バージョン 1.1.1 の公開されている `dist/index.cjs` には、`visual` という文字列は一切含まれていません。 検証済みのコミットにおけるリポジトリにも同様です。これには `docs/PLUGIN-UNSAFE-ACTIONS.md` も含まれます。

Git レベルでの観察(`VISUAL` のみ設定され、`EDITOR` と `GIT_EDITOR` は設定されていない場合): `git var GIT_EDITOR` は攻撃者のパスを返しました。 `git commit --amend` は攻撃者のスクリプトを実行しました(マーカーが書き込まれ、コミットの件名が書き換えられました)。 `git rebase -i` も同様に、rebase-todo に対して攻撃者のスクリプトを実行しました。 `VISUAL` は `EDITOR` よりも優先されます(例:`EDITOR=/bin/true VISUAL=evil` の場合、`evil` が実行されます)。

実際の実行パス全体を通して確認した結果(このコミットからビルドされたsimple-gitを使用、デフォルトオプション、危険な設定は許可しない)、`EDITOR`と`GIT_EDITOR`の両方で`GitPluginError`が発生しました。エラーメッセージは「...の使用は、allowUnsafeEditorを有効にしないと許可されません」というもので、実行はされませんでした。一方、`VISUAL`はブロックされませんでした。プラグインは脆弱性のリストが空であることを検出し、Gitが起動し、攻撃者が指定したエディタがホストユーザーとして`.git/COMMIT_EDITMSG`に対して実行され、コミットメッセージが書き換えられました。

Gitレベルでの再現例:

```sh
#!/bin/sh
set -e
rm -rf /tmp/visual-repo /tmp/GIT_VISUAL_POC /tmp/evileditor
mkdir /tmp/visual-repo && cd /tmp/visual-repo
git init -q
git config user.email a@a && git config user.name a
touch a && git add a && git commit -qm init
printf '#!/bin/sh\ntouch /tmp/GIT_VISUAL_POC\nexit 1\n' >/tmp/evileditor
chmod +x /tmp/evileditor
env -u EDITOR -u GIT_EDITOR VISUAL=/tmp/evileditor git commit --amend || true
test -e /tmp/GIT_VISUAL_POC && echo executed
```

公開APIを使用した、より高レベルな再現例:

```ts
import { vulnerabilityCheck } from '@simple-git/argv-parser';
import { spawnSync } from 'node:child_process';
const args = ['commit', '--amend'];
const env = { VISUAL: '/tmp/evileditor' };
if (vulnerabilityCheck(args, env).length === 0) spawnSync('git', args, { env });
```

- **期待される動作:** `parseEnv({ VISUAL: '/tmp/evileditor' }).vulnerabilities`には`allowUnsafeEditor`が含まれており、`allowUnsafeEditor`が有効になっていない場合、この操作は拒否されるべきです。これは、すでに`EDITOR`、`GIT_EDITOR`、および`GIT_SEQUENCE_EDITOR`に対して適用されている動作です。
- **実際の動作:** 脆弱性が報告されず、ガードは実行を許可し、Gitは攻撃者が選択したバイナリを実行します。

## 推奨される対策

`VISUAL`をエディタのソースとして扱い、Git自身のエディタ解決の優先順位が、完全にブラックリストによってカバーされるようにします。

```ts
const GitEnvKeys = {
'editor': 'allowUnsafeEditor',
'visual': 'allowUnsafeEditor',
// ...
'git_editor': 'allowUnsafeEditor',
// ...
'git_sequence_editor': 'allowUnsafeEditor',
} as const satisfies Record<string, VulnerabilityCategory>;
```

`prepareEnv`関数は`GitEnvKeys`に含まれるかどうかでフィルタリングを行うため、この単一のエントリだけでも`visual`がフィルタリングを通過し、分類されるようになります。したがって、`prepareEnv`関数または`collectConfigVulnerabilities`関数を変更する必要はありません。

注記:

- `TERM`もまた、`GitEnvKey`でもなく、`git`で始まる変数でもありません。この変数は、Gitが`VISUAL`を参照するかどうかを決定します(`TERM=dumb`または未設定の場合、`VISUAL`は無視されます)。攻撃者が環境オブジェクトを制御している場合、この変数が`VISUAL`と共に提供されることがあります。`TERM`を個別に分類すべきかどうかは、保守担当者の判断に委ねられますが、この問題を修正する際に検討する価値があります。
- `git rebase -i`コマンドも同様の危険性があります。`git_sequence_editor`は通常のエディタの解決にフォールバックするため、`VISUAL`のパスを通じて、攻撃者が用意したバイナリがrebase-todoファイルに対して実行される可能性があります。
- `docs/PLUGIN-UNSAFE-ACTIONS.md`(「テキストエディタ」)には、`VISUAL`を`EDITOR`、`GIT_EDITOR`、`GIT_SEQUENCE_EDITOR`と共にリストする必要があります。なぜなら、ドキュメントに記載されている範囲が、ユーザーが依存しているからです。
- すでに安全であり、変更は不要です。`EDITOR`、`GIT_EDITOR`、および`GIT_SEQUENCE_EDITOR`はすべて、プラグインによって正しく分類および適用されており、パーサーによる`git`で始まる変数の処理も影響を受けていません。

`test/parse-env.spec.ts` と並行して、以下の回帰テストを提案します(現在、`EDITOR`、`GIT_EDITOR`、`GIT_SEQUENCE_EDITOR`、`PAGER` をカバーしていますが、`VISUAL` のケースは未対応です)。`parseEnv({ VISUAL: '/tmp/evileditor' })` が、あらゆるケースで `allowUnsafeEditor` の脆弱性を 1 つずつ検出すること、および `vulnerabilityCheck(['commit', '--amend'], { VISUAL: '/tmp/evileditor' })` が、脆弱性リスト `[]` ではなく、その脆弱性を返すことを検証します。また、優先順位に関するテストケースも追加する価値があります。`VISUAL` と `EDITOR` が同時に設定されている場合でも、`VISUAL` が優先されるため、やはり脆弱性としてフラグが立てられるべきです。

影響を受けるパッケージ:
- npm (JavaScript) @simple-git/argv-parser: 影響を受けるバージョンは 2.0.1 未満、2.0.1 で修正済み。