FlawOpen

flawopen.com/Teardowns/cve-2026-10032-a2ui-agent-button-xss

● CVE-2026-10032 · CVSS 9.3[1] · 深刻
セキュリティ研究 · FlawOpen

CVE-2026-10032:A2UIのエージェント生成ボタンにおけるXSS

影響を受ける版では、既定で有効なBasic CatalogのopenUrlがエージェント指定URLをスキーム制限なしでwindow.openに渡していました。アドバイザリのブラウザーオリジンへの影響には、ユーザーがレンダリング済みButtonをクリックする必要があります。0.10.2以降へ更新してください。[1]

CVSS ベクトルCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N[1]
💡 わかりやすい解説 (ELI5)

アシスタントがボタンを置けるキオスクを想像してください。行き先はアシスタントが渡したカードに書かれています。キオスクは住所の有無は確認しますが、普通のWebページに向かうかは確認しません。訪問者が押す必要はありますが、行き先の規則がなければブラウザーが住所をキオスクへの命令として扱う可能性があります。修正はURLを解析してHTTPまたはHTTPSだけを許可します。[1], [2]

主要な概念と専門用語

A2UI
エージェント生成UIのオープンソースプロトコルおよびプロジェクトです。アドバイザリはweb_coreのBasic Catalogを対象とします。[1]
Basic Catalog
レンダリング済みUIアクション用のA2UI標準関数群です。既定で有効です。[1]
関数呼び出しアクション
関数と引数を指定する宣言型UIアクションです。ButtonはエージェントURLでopenUrlを呼び出せました。[1], [2]
CWE-79
Webページ生成時の入力無害化が不適切である弱点分類です。A2UIアドバイザリはこの注入に割り当てています。[1], [4]

フレームワーク対応

  • このガイドで説明する弱点の CWE 参照: CWE-79

根本原因の分析 (Root Cause)

Basic CatalogはURL引数を、スキーム方針が必要な信頼できないナビゲーション入力ではなく、開く文字列として扱っていました。APIは文字列型を確認しますが、脆弱な実装はwindow.open(args.url, '_blank')の前にURLプロトコルを制限しませんでした。エージェント制御のButton.functionCallが値をsinkに届けます。クリックは必要です。A2UIアドバイザリは被害アプリのブラウザーオリジンでのスクリプト実行を説明しています。修正はURLを解析し、http:とhttps:のみ許可してnoopener,noreferrerで開きます。このsinkの修正であり、エージェント生成UI全般の安全は保証しません。[1], [2]

CVE-2026-10032:影響バージョンとアクション境界

デプロイ条件アドバイザリが確認した内容防御策
既定Basic Catalogを使う@a2ui/web_core 0.9.0、0.9.1、0.9.2、0.10.0、0.10.1。A2UIアドバイザリはこれらを影響対象に挙げ、Basic Catalogは既定で有効としています。0.10.2以降へ更新し、デプロイ済みアプリの解決バージョンを確認します。
Button関数呼び出しがエージェント制御データから届き、ユーザーがクリックする。アドバイザリはクリックからopenUrlへの経路とブラウザーオリジンへの影響を説明します。ゼロクリックではありません。引数を信頼せず、URLを開く関数で検証します。
A2UIのReact、Lit、Angular rendererを使う。これらの統合が対象で、脆弱な境界はweb_coreの共有Basic Catalogアクションです。パッケージを確認してweb_coreを更新します。
@a2ui/web_core 0.10.2以降を実行する。メンテナーは0.10.2を修正版とし、URL解析、HTTP/HTTPS許可、noopener,noreferrerを追加しました。有効・無効スキームの回帰テストと独自openUrlの確認を続けます。

ステップ・バイ・ステップの攻撃フロー

ステップ 1

エージェントがButtonアクションを指定

A2UIデータは関数呼び出しと引数を持つButtonを定義できます。エージェント由来のアクションは信頼できないものとして扱います。[1]

ステップ 2

Basic CatalogがopenUrlを解決

Basic CatalogはopenUrlを提供します。スキーマは文字列を要求しますが、脆弱なコードはURLスキームを制限しませんでした。[1]

ステップ 3

ユーザーがButtonをクリック

アドバイザリの経路にはユーザーのクリックが必要です。ゼロクリックと表現しないでください。[1], [2]

ステップ 4

アクションがwindow.openに到達

修正前のopenUrlはプロトコルを確認せず文字列をwindow.openへ渡しました。アドバイザリは被害アプリでの実行を説明します。[1]

ステップ 5

修正済みアクションが遷移先を制限

修正はURLを解決し、HTTP/HTTPS以外を拒否し、新しい閲覧コンテキストにnoopener,noreferrerを指定します。0.10.2以降へ更新してください。[1], [2]

ソースコード比較:脆弱 vs 堅牢化

脆弱な抜粋はA2UIアドバイザリが示す実装から取得しました。PR #1707はURLを解析し、HTTP/HTTPSのみ許可して、開いたページによる元ページ操作やreferrer取得を防ぎます。型とimportは省略しており、完全なファイルやPoCではありません。[1], [2]

ソース抜粋: basic_functions.ts — OpenUrlImplementation

脆弱な実装
export const OpenUrlImplementation = createFunctionImplementation(OpenUrlApi, args => {
  if (args.url && typeof window !== 'undefined' && window.open) {
    window.open(args.url, '_blank');
  }
});
堅牢化されたセキュアパッチ
export const OpenUrlImplementation = createFunctionImplementation(OpenUrlApi, args => {
  if (args.url && typeof window !== 'undefined' && window.open) {
    const baseHref =
      typeof window.location !== 'undefined' && window.location.href
        ? window.location.href
        : undefined;
    let url: URL;
    try {
      url = baseHref ? new URL(args.url, baseHref) : new URL(args.url);
    } catch (e: any) {
      throw new A2uiExpressionError(`Invalid URL specified: ${args.url}`, 'openUrl', e);
    }

    // プロトコルを厳密に許可リスト化し、HTTP と HTTPS のみを許可します。
    if (url.protocol !== 'https:' && url.protocol !== 'http:') {
      throw new A2uiExpressionError(`Unsupported URL scheme: ${url.protocol}`, 'openUrl');
    }

    // 開いたページが元のウィンドウを操作できないよう、常に noopener と noreferrer を使用します。
    window.open(url.href, '_blank', 'noopener,noreferrer');
  }
});

エンジニアリング&システム堅牢化チェックリスト

参考資料