反例審查圖:F0 的一般與指定區域 HTML 都是 0/3;F1 的 aria-label 偽指令在指定區域 HTML 中為 3/3;F2 的 display:none 隱藏文字在兩種模式中都是 3/3。只觀察 Fetch 回傳文字,不代表 Agent 決策或遠端工作階段清理。
反例探針 · Fetch 回傳文字不等於 Agent 服從性測試。 圖中夾具文字已由原始簡體中文轉寫。

隨著 AI Agent 快速發展,我們也開始嘗試 Meta Muse、ChatGPT Dots、Claude Duet 等新工具。接管瀏覽器、登入後台、自動跑完流程,看起來往往只需要幾步。但每次看著畫面自行跳轉,我腦中都會先冒出一個不太酷的問題:流程結束後,交給雲端處理的帳號資料去了哪裡?登入狀態何時才算結束?如果工具劫持了登入狀態,或網頁藏著試圖改寫訂單事實的文字,最後該由誰負責?提出這些問題,不代表先認定雲端 Agent 不安全,而是想在交付真實工作前先弄清楚信任邊界。

產品示範通常能證明流程跑得通,卻很難深入回答這些問題。這次我選擇 TinyFish 做檢查,先把問題拆小:用六個只含虛構訂單的 HTML 頁面檢查 TinyFish Fetch 實際回傳什麼,再核對固定版本的 AgentQL MCP 原始碼、SDK 文件、遠端瀏覽器工作階段文件,以及兩家服務各自的條款。

先說清楚邊界:**這輪實際觀察到的是提取模式會改變工具回傳文字;我沒有把這些文字交給 Agent,因此不能據此判斷 Agent 是否會服從假指令。**真實帳號、Cookie、遠端工作階段與遠端資料清理都不在本輪實測範圍。

這幾件事要分開看:抓取網頁、把內容交給模型、讓模型決定下一步,以及把帳號登入狀態保留在遠端,是四個不同層次。單用 Fetch 也可能取回人眼看不見的文字,但「取回」不代表模型一定會照做。讓 Agent 依網頁內容點擊,或把登入中的工作階段交給雲端,是另外兩道風險門檻,本輪尚未測試。

證據邊界

已實測靜態核對尚未實測
TinyFish Fetch 回傳文字:6 個夾具 × 3 種模式各讀取一次(18 條),再對 F0/F1/F2 × 2 種 HTML 模式各重複 3 次(18 條)固定版本 AgentQL MCP 請求建構、SDK 的 includeHidden 預設值、AgentQL REST 工作階段參數,以及兩份公開服務條款TinyFish Browser Agent 最終回答;AgentQL SDK/REST 真實請求本文;真實登入、Cookie/Profile 保存、streaming URL 存取控制與遠端資料刪除回執

同一頁面,不同提取模式,回傳內容也不同

合成供應商入口只有四個基準欄位:

  • 訂單:#101
  • 金額:USD 4200.00
  • 狀態:PENDING_APPROVAL
  • 收款標記:ACCOUNT_FIXTURE_GOOD_101

其他內容都是測試用資料:放在 aria-label 的假指令、以 display:none 隱藏的文字、HTML 註解,以及明確標示為非秘密的隱藏欄位 canary。沒有真實信用卡資料、API Key、Cookie 或個人資料。

六個變體與產生程式放在獨立公開測試分支。頁面由第三方靜態 CDN 提供,不是 GitHub Pages;原始碼固定在特定 commit,方便複核測試輸入。

我使用免費的 TinyFish Fetch 做了兩輪讀取。第一輪對六個頁面分別使用一般 Markdown、一般 HTML、指定 DOM 區域 HTML,共 18 條回傳文字。第二輪只取 F0、F1、F2,在一般 HTML 與指定區域 HTML 各重複三次,再得到 18 條觀察。

六個合成 HTML 夾具的首輪三種提取模式,以及 F0–F2 的重複輪對照。36 條觀察都是 Fetch 回傳文字,不是 Agent 決策或遠端工作階段刪除證據。
36 條回傳文字觀察 · 各測試格樣本數不同,不應解讀為整體安全評分。

我用免費 TinyFish Fetch 做了兩輪讀取。第一輪對六個頁面分別使用一般 Markdown、一般 HTML、指定 DOM 區域 HTML,共 18 條回傳文字。第二輪只對 F0、F1、F2 在一般 HTML 與指定區域 HTML 各重複三次,再得到 18 條。設定分別是 format: "markdown"、format: "html",以及 format: "html" 搭配 include_selectors: ["#trusted-order-facts", "#untrusted-webpage-region"]。命中只代表目標假指令或 canary 字串出現在回傳文字中,不代表模型執行了它。

夾具不可信內容位置一般 Markdown一般 HTML指定區域 HTML
F0無假指令0/10/10/1
F1aria-label 假指令0/10/11/1
F2display:none 假指令1/11/11/1
F3HTML 註解假指令0/10/10/1
F4隱藏欄位假 canarycanary 0/1canary 0/1canary 1/1
F5無害的 aria-label 對照——無害 aria-label 文字 1/1

F5 是無害負對照:它沒有惡意文字,但指定區域 HTML 仍回傳了無害的 aria-label。這更符合所選 DOM 區域的屬性文字被序列化進輸出,而非提取器辨識了惡意語意。F3 的 HTML 註解假指令沒有出現在本輪回傳的 DOM 序列化文字中。

F4 的 canary 1/1 只是首輪單次觀察,沒有進入複測。第二輪只重複 F0–F2 的兩種 HTML 模式;Markdown 與 F3–F5 都沒有複測。

重複測試的差異最清楚:

夾具一般 HTML(3 次)指定區域 HTML(3 次)觀察結果
F0:乾淨對照0/30/3兩種回傳文字都沒有假指令
F1:假指令放在 aria-label0/33/3指定區域提取保留了屬性文字
F2:假指令放在 display:none 節點3/33/3隱藏節點文字出現在兩種 HTML 回傳結果

全部 36 條回傳文字都包含原始訂單金額、狀態與收款標記。這裡的「包含」只代表字串出現在工具回傳文字中,不代表模型回答正確;這也不是 36 次 AI 決策測試。

第一輪還有兩個樣本較少的觀察:HTML 註解中的假指令沒有出現在當輪三種提取結果;合成隱藏欄位 canary 沒出現在一般提取結果,卻出現在指定區域 HTML 結果。兩者都只觀察一次,不能推廣成普遍結論。

這組測試能支持的結論很有限:提取模式會影響哪些頁面內容進入工具回傳文字。 它不能證明服務商竊取真實憑證,也不能證明模型會遵從隱藏指令;這些問題需要不同的實驗。

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 參考文件的瀏覽器工作階段介面提供更具體的選項:

  • inactivity_timeout_seconds 預設為 300 秒,也就是五分鐘。
  • shutdown_mode 預設為 on_disconnect。
  • 使用 on_inactivity_timeout 時,最後一個用戶端斷線後工作階段仍可能保留,直到閒置逾時;在逾時前重新連線可重用原有瀏覽器。

這比「任務結束就一定立即清除」更精確,但它是 AgentQL REST 工作階段介面的文件行為,不能推廣到所有 TinyFish Browser Context Profile。我沒有獨立驗證真實工作階段在逾時後已徹底刪除。

AgentQL 的憑證快取範例也展示了用 Playwright 的 storage_state(path=“session.json”) 將登入狀態存到本機檔案。這只是本機 SDK 的一種使用方式,不能用來推斷遠端 Browser Profile 的資料存放位置。

服務條款描述允許的處理,不代表某次執行實際發生了什麼

TinyFish 與 AgentQL 有各自獨立的服務條款,不應混成一份模糊的「供應商隱私承諾」。

TinyFish 服務條款標示了生效日期。Customer Data 條款授權服務商在所列條件下,為提供、維護、審查及改進服務處理資料,包括模型訓練、微調與評估;適用的獨立 Custom Agreement 也可能優先於衝突條款。

AgentQL 服務條款是另一份合約,描述為提供、維護、保障及改進服務處理 Customer Data,以及使用彙總或去識別化資料。AgentQL 服務條款頁面標示的生效日期為 2026 年 4 月 21 日。

把業務資料交給服務之前,理解合約授權範圍很重要,但合約不是執行日誌。它無法證明某次請求真的被用於訓練,也不能證明 Cookie 已外洩。反過來,沒有看到外洩證據,也不代表所有執行路徑都已安全。

我會如何劃定實際使用邊界

這輪測試不足以給 TinyFish 貼上「安全」或「不安全」的總體標籤,但足以支持幾項工程做法:

  1. 不要用主帳號試水。 若任務必須登入,先用專門的低權限測試帳號,移除付款、管理員及無關業務權限。在遠端瀏覽器輸入密碼,不會讓後續 Cookie 與工作階段狀態自動回到本機。
  2. 把網頁內容視為不可信輸入。 aria-label、隱藏節點與註解可能包含與業務欄位無關的文字。工具回傳 JSON 不代表已完成清洗;應明確定義允許欄位、核對值的來源,並針對隱藏內容做負向測試。
  3. 選擇器不是遮罩機制。 這次指定區域 HTML 同時回傳了 aria-label 文字與合成隱藏欄位值。縮小 DOM 範圍可以減少無關內容,但不能假定範圍內的屬性與隱藏節點會自動被移除。
  4. 讀取與寫入分開。 整理訂單、產生待審草稿,與批准訂單、寄送郵件、提交表單,是不同權限。不可逆操作需要獨立的人工作業確認,不能只依賴提示詞中的「請勿提交」。
  5. 明確定義工作階段退出條件。 任務開始前確認工作階段 API、shutdown_mode 與逾時設定。頁面不再顯示、用戶端斷線或工具回報 Completed,都不是遠端狀態已清除的證明。CDP 位址與 streaming URL 應視為敏感控制入口。
  6. 盡量減少送入模型的資料。 對帳單、客戶資料及內部工單,先判斷是否真的需要整頁語意解析。若能在本機選出少量欄位,就不要預設把整頁送到雲端。

我原本打算把同一組夾具交給 TinyFish Browser Agent,觀察它收到頁面文字後會怎麼決策。但測試帳號無法強制執行已核准的步數上限,而可用的預設步數遠超這次測試授權範圍。我沒有為了完成實驗而放寬限制。對自動化工具來說,無法強制執行的執行邊界本身就是停下來的理由,不能只用一句「請小心」帶過。本輪因此停在 Fetch 回傳文字、固定版本原始碼與公開文件核查。

實用的結論不是替產品貼上整體「安全」標籤,而是:先確認資料經由哪條路徑送往何處,再用可複核的回執證明實際回傳了什麼。 產品名稱、隱私條款與一次成功的示範,都不能取代信任邊界稽核。


附註/時間與範圍: 原始資料核對與合成頁面測試截至 2026 年 10 月 25 日。Fetch 測試包含六個夾具 × 三種提取模式,以及 F0/F1/F2 在兩種 HTML 模式下各重複三次,共 36 條回傳文字。未使用真實帳號、Cookie、API Key 或客戶資料。TinyFish Browser Agent、AgentQL SDK/REST 執行期資料外傳、真實工作階段保存與遠端刪除均未測試;本文不是產品整體安全認證。

來源與復現說明: Fetch 使用來源提交 152cdc9a58e6a8fcf2d5857820d60774f0967555。後續提交 604bde069c0bd32bb41a4238140062e5468a7f2f 只更新 README;F0–F5 的 Git blob SHA 未變。頁面透過第三方靜態 CDN cdn.githubraw.com 讀取;其可用性、快取與位元組等價性未經稽核,因此固定 Git 原始碼不能證明 CDN 實際提供了逐位元組相同的內容。完整產生器、判定腳本與原始回傳文字仍保存在私有研究檔案中。