flawopen.com/Teardowns/cve-2026-10032-a2ui-agent-button-xss
CVE-2026-10032:A2UIのエージェント生成ボタンにおけるXSS
影響を受ける版では、既定で有効なBasic CatalogのopenUrlがエージェント指定URLをスキーム制限なしでwindow.openに渡していました。アドバイザリのブラウザーオリジンへの影響には、ユーザーがレンダリング済みButtonをクリックする必要があります。0.10.2以降へ更新してください。[1]
アシスタントがボタンを置けるキオスクを想像してください。行き先はアシスタントが渡したカードに書かれています。キオスクは住所の有無は確認しますが、普通のWebページに向かうかは確認しません。訪問者が押す必要はありますが、行き先の規則がなければブラウザーが住所をキオスクへの命令として扱う可能性があります。修正はURLを解析してHTTPまたはHTTPSだけを許可します。[1], [2]
主要な概念と専門用語
A2UI- エージェント生成UIのオープンソースプロトコルおよびプロジェクトです。アドバイザリはweb_coreのBasic Catalogを対象とします。[1]
Basic Catalog- レンダリング済みUIアクション用のA2UI標準関数群です。既定で有効です。[1]
フレームワーク対応
- このガイドで説明する弱点の 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の確認を続けます。 |
ステップ・バイ・ステップの攻撃フロー
ソースコード比較:脆弱 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');
}
});
エンジニアリング&システム堅牢化チェックリスト
- ✓@a2ui/web_coreの直接・推移依存を0.10.2以降へ更新し、lockfileと本番bundleの解決バージョンを確認します。[1]
- ✓Basic CatalogおよびReact、Lit、Angular rendererを使うアプリを棚卸しし、エージェントUIがButton関数呼び出しを指定できるか調べます。[1]
- ✓ナビゲーションsinkでURLを検証します。解析・解決後にHTTP/HTTPSなど許可したスキームだけを認め、文字列前方一致に頼りません。[2]
- ✓外部遷移には
noopener,noreferrerを使い、ユーザー同意方針も別途適用します。異なるリスクへの対策です。[2] - ✓HTTP/HTTPSと相対URLの解決、不正URLと非HTTP(S)スキームの
window.open前の拒否を単体テストで確認します。[2] - ✓エージェント作成のラベルや引数は信頼できないデータです。rendererの無害化を仮定せず各アクションのsinkで検証します。[1], [2]