CVEステータス: リクエスト済み、割り当て待ち。この脆弱性は GHSA-q94x-p9rc-q89f として公開されています。CVEが割り当てられた時点で、このリポジトリは
CVE-YYYY-NNNNN-pasteguard-PoCに改名され、このバナーはCVEリンクに置き換えられます。
| 研究者 | Dostxodjayev Abdullox (@squeeze440) |
| アドバイザリ | GHSA-q94x-p9rc-q89f |
| CVSS 3.1 | 7.6 (High) |
| 脆弱性 | CWE-352, CWE-942 |
概要: PasteGuard 0.9.1 の LLMプロキシルートにおける CORS/CSRF 保護の欠如により、リモート攻撃者(オペレータのブラウザが訪問する任意のWebサイト、またはローカルネットワーク上の任意のホスト)が、PasteGuard のサーバーサイドフォールバックAPIキーを使用して、オペレータが設定した OpenAI/Anthropic API への認証済みリクエストをトリガーし、そのレスポンスを読み取ることができます。これは /openai/v1/chat/completions(および同様の /anthropic/v1/messages)へのクロスオリジン fetch() を通じて行われます。
製品: PasteGuard (github.com/sgasser/pasteguard)
テスト済みバージョン: コミット 100718191499c52934f3b2ccca72ca16373fb765 (2026-07-30)、package.json バージョン 0.9.1
推定 CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H — 7.6 (High)
UI:R — オペレータは PasteGuard の実行中にブラウザでページ(任意のサイトの任意のページ)を開いている必要がありますが、それ以外の操作は不要です。C:L / I:L — 攻撃者自身のJSは、攻撃者が選択したプロンプトに対する自身のレスポンスのみを読み取ります(オペレータの過去の会話やダッシュボードデータはCORSから正しく除外されているため読み取れません)が、それでも稼働中の資金提供済みアカウントを確認・使用し、オペレータの身元でアカウントの使用状況・監査証跡を汚染します。A:H — 現実的で無制限の影響: 攻撃者ページはこのリクエストを無限にループさせることができ、オペレータのプロバイダーアカウント/クォータに対して実際に課金される使用量を発生させます(金銭的流出、レート制限の枯渇やプロバイダー側の不正利用フラグの可能性)。これを妨げるレート制限やオリジンチェックは存在しません。S:U — 影響は同一の信頼境界内に留まります(プロキシ自身が設定した認証情報が使用されるため)。別のセキュリティ権限を越えることはありません。詳細:
PasteGuard の HTTP レイヤーは、ダッシュボードを除くすべてのルートに寛容なワイルドカード CORS ポリシーを適用しています:
src/index.ts:40-45 — const corsMiddleware = cors();(オプションなしの Hono の cors() はデフォルトで Access-Control-Allow-Origin: * になります)が app.use("*", ...) を介してすべてに適用され、/dashboard と /dashboard/* のみが明示的に除外されています。すぐ上のコメント(src/index.ts:34-39)は、作者が ダッシュボード の CORS 露出について慎重に検討したことを示していますが、その下のプロキシルートには同じ推論を拡張していません。src/config.ts:153 — host: z.string().default("0.0.0.0")、config.example.yaml:13 で実際に確認済み。プロキシはデフォルトでループバックだけでなくすべてのインターフェースでリッスンするため、オペレータ自身のブラウザからだけでなく LAN からも到達可能です。src/providers/openai/client.ts:48-53:
// Use client's auth header if provided, otherwise fall back to config
if (authHeader) {
headers.Authorization = authHeader;
} else if (config.api_key) {
headers.Authorization = `Bearer ${config.api_key}`;
}
受信リクエストに Authorization ヘッダーが含まれていない場合、PasteGuard は自身のサーバー保持の providers.openai.api_key を送信リクエストに黙って付加します。config.example.yaml:22-24 はこれを「Apps & APIs」ユースケース向けの意図的な便利機能(「クライアントが認証ヘッダーを送信しない場合のオプションのフォールバック」)として文書化しています。src/providers/anthropic/client.ts:37-61 は /anthropic/v1/messages に対して同一のフォールバックパターンを実装しています(x-api-key/Authorization が config.api_key にフォールバック)— 同じ根本原因の兄弟インスタンスであることが確認されていますが、エンドツーエンドで個別に再検証はされていません(以下の PoC スコープを参照)。ワイルドカード CORS ポリシーがこのフォールバック認証情報を保持するルートの前に位置するため、任意のオリジンが (1) ブラウザに Authorization ヘッダーなしでリクエストを送信させ、サーバーに自身の実際の API キーを付加させ、(2) Access-Control-Allow-Origin: * が存在するため JSON レスポンスを読み取ることができます。これらのルートを保護する Origin/Referer チェックや CSRF トークンはありません。
概念実証(静的な追跡だけでなく、動的に確認済み):
config.yaml を providers.openai.base_url: http://127.0.0.1:9091(モックアップストリーム)と providers.openai.api_key: "sk-VICTIM-SECRET-DO-NOT-LEAK-12345"、pii_detection.enabled: false(モック検出器の /health のみ、完全な GLiNER モデルサービスを必要としないため)、secrets_detection.enabled: false で設定しました。bun run src/index.ts で PasteGuard を起動し、/health → 200 を確認しました。127.0.0.1:9091 でモックアップストリーム(mock_upstream.py)を起動し、受信した Authorization ヘッダーをログに記録しました。http://127.0.0.2:8001/attack.html(Chrome のサイト分離ルールに従った異なるリテラルループバック IP — 同一オリジンの localhost テストではない)から、python3 -m http.server 8001 --bind 127.0.0.2 を介して実際の攻撃者ページを配信しました。ページの読み込み時の唯一のアクション:
fetch("http://127.0.0.1:3000/openai/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "cross-origin drive-by call, attacker supplied NO api key" }] })
}).then(r => r.json()).then(data => { /* render on page */ });
http://127.0.0.2:8001/attack.html に誘導しました。DevTools のネットワーク検査で確認:
origin: http://127.0.0.2:8001、sec-fetch-site: cross-site、攻撃者ページから送信された Authorization ヘッダーはなし。access-control-allow-origin: *、HTTP 200、JSON ボディは攻撃者ページの JS から完全に読み取り可能。../evidence/cross_origin_csrf_success.png。RECEIVED AUTH HEADER: Bearer sk-VICTIM-SECRET-DO-NOT-LEAK-12345 — PasteGuard がオペレータの実際に設定されたキーをサーバーサイドで付加したことを証明しており、攻撃者ページはそれを知ることも提供することもありませんでした。生の証拠: ~/engagements/pasteguard/evidence/cross_origin_csrf_success.png、~/engagements/pasteguard/evidence/mock_upstream_log.txt。
/anthropic/v1/messages ルートは、同一のフォールバック + ワイルドカード CORS パターンを共有することが静的に確認されました(上記のファイル:行)が、/openai で根本原因がすでに確認された後は、セッションの「深さ優先・5分ルール」ガイダンスを考慮し、ライブブラウザ PoC で個別に再実行はされていません。
影響: PasteGuard オペレータのブラウザが訪問する任意のWebサイト(悪意のある広告、侵害されたサイト、またはデフォルトの 0.0.0.0 バインドにより同一 LAN 上の攻撃者)が、オペレータのローカル PasteGuard インスタンスを介して、オペレータ自身の OpenAI/Anthropic アカウントを通じて無制限の課金リクエストを黙って実行できます。タブを開いている以外のユーザー操作は不要で、オペレータがプロバイダーの請求ダッシュボードを監視しない限り気づく方法はありません。これは直接的な金銭的悪用 / クォータ枯渇のベクターであり、二次的にはオペレータのプロバイダーアカウントの使用状況・監査証跡を攻撃者が選択したコンテンツで汚染します。
脆弱性:
修復(提案):
api_key が設定されている場合、ワイルドカード cors() ミドルウェアを /openai、/anthropic、/codex(およびサーバー保持の認証情報を使用するように拡張された場合は /api/mask)に適用しないでください。少なくとも、これらのルートで許可されるオリジンを明示的/設定可能にし(例: ブラウザ拡張機能の既知のオリジンのみ)、* ではなくクロスオリジンアクセスなしをデフォルトにしてください。src/index.ts で /dashboard にすでに適用されている推論を反映してください。src/config.ts:153 と config.example.yaml:13 で server.host のデフォルトを 0.0.0.0 から 127.0.0.1 に変更し、すべてのインターフェースでバインドするには明示的なオプトインを要求してください。クレジット: Dostxodjayev Abdullox
報告チャネル: リポジトリに SECURITY.md は存在しません(gh api repos/sgasser/pasteguard/contents/SECURITY.md → 404 で確認、監査時に再確認済み)。以前に公開されたセキュリティアドバイザリもありません(gh api repos/sgasser/pasteguard/security-advisories → [])。したがって、これは以前に開示された問題の重複ではないようです。推奨チャネル: https://github.com/sgasser/pasteguard/security/advisories/new の GitHub のデフォルトのプライベート脆弱性報告フロー。