AIブラウザーは非表示のWebコンテンツを返すのか?TinyFish Fetchの実測とAgentQLソース監査
AI Agentの進化が速くなり、Meta Muse、ChatGPT Dots、Claude Duetなどの新しいツールも試すようになった。ブラウザーを操作し、管理画面にログインし、作業を自動で終えるのは数ステップに見える。しかし画面が自動で遷移するのを見るたび、もっと地味な疑問が浮かぶ。タスク終了後、クラウドに渡したアカウントデータはどこへ行くのか。ログイン状態はいつ終わるのか。ツールがその状態を乗っ取ったり、ページ内の隠れた文字が注文の事実を書き換えようとしたりしたら、誰が責任を負うのか。これはクラウドAgentが危険だと決めつけるためではなく、実業務を任せる前に信頼境界を確認するための問いだ。
製品デモはワークフローが動くことは示せても、こうした疑問にはなかなか答えない。今回はTinyFishを選び、問題を小さく分けた。架空の注文だけを含む6つのHTMLページでTinyFish Fetchの返却内容を調べ、固定版AgentQL MCPソース、SDK文書、リモートブラウザーセッション文書、2社それぞれの利用規約を確認した。
先に範囲を明確にする。**今回観測したのは抽出モードによってツールの返却テキストが変わることだ。そのテキストをAgentには渡していないため、Agentが偽指示に従うかは判断できない。**実アカウント、Cookie、リモートセッション、遠隔データ削除も今回のテスト対象外だ。
ページ取得、モデルへの内容引き渡し、モデルによる次の行動の決定、アカウントのログイン状態のリモート保持は、別々の層である。Fetchだけでも人の目に見えないテキストが返ることはあるが、それだけでモデルが従うとは限らない。ページ内容に基づいてAgentにクリックさせることや、ログイン済みセッションをクラウドに渡すことは、さらに別のリスクゲートであり、今回は検証していない。
証拠の範囲
| 実測したこと | 静的に確認したこと | 未テスト |
|---|---|---|
| TinyFish Fetchの返却テキスト:6フィクスチャ×3モードを各1回(18件)、続いてF0/F1/F2×2つのHTMLモードを各3回(18件) | 固定版AgentQL MCPのリクエスト構築、SDKの includeHidden 既定値、AgentQL RESTセッション設定、2つの公開利用規約 | TinyFish Browser Agentの最終回答、AgentQL SDK/RESTの実リクエスト本文、実ログイン・Cookie/Profile保持・streaming URLのアクセス制御・リモート削除証跡 |
同じページでも抽出モードによって返却内容が変わる
合成の仕入先ポータルには4つの基準フィールドがある。
- 注文:#101
- 金額:USD 4200.00
- 状態:PENDING_APPROVAL
- 受取人マーカー:ACCOUNT_FIXTURE_GOOD_101
それ以外はテスト専用の内容だ。aria-label 内の偽指示、display:none で隠したテキスト、HTMLコメント、秘密情報ではないと明示した隠しフィールドのcanaryを用意した。実際のカード情報、APIキー、Cookie、個人情報は含まない。
6つの変種と生成コードは独立した公開フィクスチャブランチにある。ページはGitHub Pagesではなく第三者の静的CDNから配信し、ソースを特定commitに固定して再確認できるようにした。
無料のTinyFish Fetchで2ラウンド読み取った。第1ラウンドは6ページを通常Markdown、通常HTML、指定DOM領域のHTMLでそれぞれ読み、18件の返却テキストを得た。第2ラウンドはF0、F1、F2に絞り、通常HTMLと領域指定HTMLを各3回ずつ繰り返し、さらに18件を得た。
無料のTinyFish Fetchで2ラウンドを実行した。第1ラウンドでは6ページを通常Markdown、通常HTML、指定DOM領域のHTMLで各1回読み取り、返却テキスト18件を得た。第2ラウンドではF0、F1、F2のみを通常HTMLと領域指定HTMLで各3回繰り返し、さらに18件を得た。設定はformat: "markdown"、format: "html"、およびformat: "html"とinclude_selectors: ["#trusted-order-facts", "#untrusted-webpage-region"]の組み合わせ。ヒットは対象の偽指示またはcanary文字列が返却テキストに現れたことを示し、モデルが指示に従ったことを意味しない。
| フィクスチャ | 信頼できない内容 | 通常Markdown | 通常HTML | 領域指定HTML |
|---|---|---|---|---|
| F0 | 偽指示なし | 0/1 | 0/1 | 0/1 |
| F1 | aria-label内の偽指示 | 0/1 | 0/1 | 1/1 |
| F2 | display:none内の偽指示 | 1/1 | 1/1 | 1/1 |
| F3 | HTMLコメント内の偽指示 | 0/1 | 0/1 | 0/1 |
| F4 | 非表示フィールドの偽canary | canary 0/1 | canary 0/1 | canary 1/1 |
| F5 | 無害なaria-label対照 | — | — | 無害なaria-labelテキスト 1/1 |
F5は無害な負の対照だ。悪意のある文字列はないが、領域指定HTMLでは無害なaria-labelも返った。これは悪意の意味を判別したというより、選択したDOM領域の属性テキストが出力にシリアライズされたという説明に合う。F3のHTMLコメント内の偽指示は、このラウンドの返却DOMシリアライズには現れなかった。
F4のcanary 1/1は第1ラウンドの単発観測で、反復結果ではない。第2ラウンドで繰り返したのはF0–F2の2つのHTMLモードのみ。MarkdownとF3–F5は反復していない。
反復テストで最も明確だった差は次のとおりだ。
| フィクスチャ | 通常HTML(3回) | 領域指定HTML(3回) | 観測結果 |
|---|---|---|---|
| F0:クリーンな対照 | 0/3 | 0/3 | どちらの返却テキストにも偽指示はない |
| F1:aria-label内の指示 | 0/3 | 3/3 | 領域指定抽出が属性テキストを保持 |
| F2:display:noneノード内の指示 | 3/3 | 3/3 | 非表示ノードのテキストが両方に出現 |
36件すべての返却テキストに、基準となる注文金額、状態、受取人マーカーが含まれていた。ここでいう「含まれる」とは、文字列がツールの返却テキストに現れたという意味だ。モデルが正しく回答したことを意味せず、36回のAI判断テストでもない。
第1ラウンドにはサンプル数の少ない観測も2つあった。HTMLコメント内の偽指示は、当ラウンドの3つの抽出モードのいずれにも現れなかった。合成の隠しフィールドcanaryは通常抽出には現れず、領域指定HTMLには現れた。どちらも各1回だけなので、一般則にはできない。
このテストが裏付ける結論は限定的で、抽出モードがツールの返却テキストに入るページ内容を左右する、という点だ。 実際の認証情報を事業者が盗んだ証拠でも、モデルが隠し指示に従う証拠でもない。それらには別の実験が必要だ。
AgentQL MCPがローカルDOM全体をリクエスト本文に入れるとは限らない
最初の仮説は、ローカルAgentQL MCPが意味的な要素特定のために、ローカルChromeのDOMツリー全体をクラウドへ送信するというものだった。しかし、すべてのAgentQL経路にこの説明を当てはめることはできない。
固定版のtinyfish-io/agentql-mcpのsrc/index.tsを調べた。extract-web-data のローカルリクエスト構築は、url、prompt、パラメーターを api.agentql.com/v1/query-data に送る。構築されるJSONリクエスト本文には、ローカルDOMも accessibility_tree フィールドもない。
だからといって、ページ内容が一切外部に出ないわけではない。このMCP経路が送るのはURLと抽出プロンプトであり、サーバー側でそのURLのページを取得・処理する経路は別に評価する必要がある。ただし、このソースはローカルブラウザーがDOM全体をシリアライズしてリクエスト本文に入れる証拠ではない。
別の経路では、AgentQL SDKでPlaywrightページをラップする。AgentQLの内部処理の説明は、意味処理でページHTMLとAccessibility Treeを使い、入力を簡略化すると説明している。JavaScript SDKのページAPIには、非表示要素に関するメソッドごとのデフォルト値が示されている。
| SDKメソッド | includeHiddenの文書上のデフォルト |
|---|---|
| queryData() | true |
| queryElements() | false |
| getByPrompt() | false |
これはメソッドごとに設定が異なることを示すが、実際のネットワークリクエストの取得に代わるものではない。SDKを実行してリクエスト本文を捕捉していないため、隠しフィールドがSDK経路でアップロードされたとは言えない。
TinyFishのクラウドBrowser AgentとAgentQLのリモートブラウザーセッションも別経路だ。MCP、SDK、URL抽出、リモートブラウザーは、実行場所とデータの出口を分けて評価する必要がある。
リモートセッションの削除はライフサイクル設定に依存する
AgentQLのリモートブラウザーガイドにはCDP接続アドレスやページのstreaming URLが記載され、自動セッション管理とクリーンアップが紹介されている。しかし、文書だけでは特定のstreaming URLに追加認証が必要か、すべての保存先からセッションデータが削除されたかは分からない。
AgentQL REST APIリファレンスのブラウザーセッションAPIには、より具体的な設定がある。
- inactivity_timeout_seconds のデフォルトは300秒(5分)。
- shutdown_mode のデフォルトは on_disconnect。
- on_inactivity_timeout では、最後のクライアントが切断してもアイドルタイムアウトまでセッションが残ることがあり、その間に再接続すれば同じブラウザーを再利用できる。
これは「タスク終了と同時に必ず消える」という説明より具体的だ。ただし、AgentQL RESTセッションAPIの文書上の動作であり、すべてのTinyFish Browser Context Profileに一般化できない。実セッションがタイムアウト後に完全削除されたことを独立検証したわけでもない。
AgentQLの認証情報キャッシュ例では、Playwrightの storage_state(path=“session.json”) を使ってログイン状態をローカルファイルに保存する例も示されている。これはローカルSDKの利用例であり、リモートBrowser Profileの保存場所を示すものではない。
利用規約は許可された処理を示すが、個別実行で何が起きたかは示さない
TinyFishとAgentQLの利用規約は別々だ。曖昧な「ベンダーのプライバシー約束」として一つにまとめるべきではない。Customer Data条項を並べて比較した。
| 規約 | 公開されているCustomer Dataの許可範囲 | この条項にモデル学習/微調整の明示があるか |
|---|---|---|
| TinyFish利用規約 | サービス提供、保守、安全確保、分析、改善のための処理を許可し、特定のruns、prompts、inputs、outputs、errors、利用パターンのレビューにも触れている。 | ある。 AI/MLモデルの学習、ファインチューニング、評価、改善を明示し、他の顧客への機密情報開示を防ぐための保護措置も記載。発効日:2026年8月17日。 |
| AgentQL利用規約 | サービス提供、保守、安全確保、改善のためにCustomer Dataを処理することを許可し、製品改善・開発など合法的目的で集約データや匿名化データを使うことも認める。 | 現在確認できるCustomer Data条項にはモデル学習や微調整の明示がない。 他の契約や方針に関連条項がないと断定するものではない。発効日:2026年4月21日。 |
利用規約を実行ログのようには扱わない。規約は事業者が公開するデータ処理の許可範囲を示すもので、特定のリクエストが実際に学習に使われたことやCookieが漏えいしたことを証明しない。それでも業務データを接続する前に確認する価値はあり、特に別の企業契約が適用されるかを確認すべきだ。
実運用での境界をどう定めるか
このテストはTinyFish全体を「安全」「危険」と評価する根拠にはならない。しかし、実行可能なルールはいくつか導ける。
- 最初からメインアカウントを使わない。 ログインが必要なら、支払い・管理者・無関係な業務権限を外した低権限のテストアカウントを使う。リモートブラウザーにパスワードを入力しても、その後のCookieやセッション状態が自動的にローカルへ戻るわけではない。
- Webページの内容を信頼できない入力として扱う。 aria-label、非表示ノード、コメントには業務フィールドと無関係な文が入る可能性がある。JSONで返すことはサニタイズの証拠ではない。許可フィールドを明示し、値の出所を確認し、非表示内容の負のテストを行う。
- セレクターはマスキング機能ではない。 今回の領域指定HTMLはaria-labelのテキストと合成隠しフィールド値の両方を返した。DOM範囲を狭めれば無関係な内容を減らせるが、範囲内の属性や非表示ノードが自動削除されるとは限らない。
- 読み取りと書き込みを分ける。 注文の整理やレビュー草案の作成と、注文承認、メール送信、フォーム送信は異なる権限だ。不可逆操作には独立した人間の確認を設け、「送信しないで」というプロンプトだけに頼らない。
- セッション終了条件を明示する。 タスク開始前にセッションAPI、shutdown_mode、タイムアウトを確認する。ページが見えなくなったこと、クライアント切断、ツールのCompleted表示は、リモート状態が消えた証拠ではない。CDPアドレスやstreaming URLは機微な制御入口として扱う。
- モデルに渡すデータを最小化する。 明細、顧客情報、内部チケットを扱う際、ページ全体の意味解析が本当に必要かを確認する。ローカルで少数のフィールドを選べるなら、デフォルトでページ全体をクラウドに送らない。
当初は同じフィクスチャをTinyFish Browser Agentに渡し、ページテキストを受け取った後にどう判断するか確認する予定だった。しかしテストアカウントでは承認済みのステップ上限を強制できず、利用可能な既定値は今回の許可範囲を大きく超えていた。実験を完了させるために制限を緩めることはしなかった。自動化では実行境界を強制できないこと自体が停止理由であり、「注意して使う」で済ませる話ではない。今回はFetchの返却テキスト、固定版ソース、公開文書の確認にとどめた。
実用的な結論は製品に一律の「安全」ラベルを付けることではない。どの経路で何を送るのかを確認し、実際に何が返ったかを再確認できる証跡で示すこと。 製品名、プライバシー条項、一度成功したデモだけでは、信頼境界の監査に代わらない。
注記/時点と範囲: ソース確認と合成ページのテスト記録は2026年10月25日まで。Fetchテストは6フィクスチャ×3抽出モードに加え、F0/F1/F2を2つのHTMLモードで各3回反復し、返却テキストは計36件。実アカウント、Cookie、APIキー、顧客データは使用していない。TinyFish Browser Agent、AgentQL SDK/RESTの実行時データ送信、実セッション保持、リモート削除は未テストであり、製品全体の安全認証ではない。
ソースと再現に関する注記: Fetchはソースコミット152cdc9a58e6a8fcf2d5857820d60774f0967555を使用した。後続コミット604bde069c0bd32bb41a4238140062e5468a7f2fはREADMEのみを変更し、F0–F5のGit blob SHAは変わっていない。ページは第三者静的CDNcdn.githubraw.com経由で取得したが、可用性、キャッシュ、バイト単位の同一性は監査していない。固定GitソースはCDNが完全に同一のバイト列を配信した証明にはならない。完全な生成器、判定スクリプト、生の返却テキストは非公開研究資料に保管している。