Gibt ein KI-Browser versteckte Webinhalte zurück? TinyFish-Fetch-Test und AgentQL-Quellcodeprüfung
Mit der schnellen Entwicklung von AI Agents probieren wir auch neue Werkzeuge wie Meta Muse, ChatGPT Dots und Claude Duet aus. Einen Browser zu übernehmen, sich in einem Backend anzumelden und einen Ablauf automatisch abzuschließen, sieht oft nach wenigen Schritten aus. Wenn sich der Bildschirm selbstständig durch Seiten bewegt, kommt mir jedoch eine weniger spektakuläre Frage in den Sinn: Wo landen die Kontodaten, die an die Cloud übergeben wurden, nach Ende des Ablaufs? Wann endet der Login-Zustand? Und wer trägt die Verantwortung, wenn ein Tool diesen Zustand missbraucht oder eine versteckte Seitenanweisung Bestelldaten verfälschen soll? Diese Fragen unterstellen nicht, dass Cloud-Agenten unsicher sind; sie sollen die Vertrauensgrenze klären, bevor echte Arbeit übergeben wird.
Produktdemos zeigen meist, dass ein Ablauf funktioniert, beantworten solche Fragen aber selten im Detail. Für diese Prüfung habe ich TinyFish gewählt und das Problem eingegrenzt: Ich untersuchte die Rückgaben von TinyFish Fetch anhand von sechs HTML-Seiten mit ausschließlich erfundenen Bestellungen und prüfte zusätzlich den gepinnten AgentQL-MCP-Quellcode, SDK-Dokumentation, Dokumentation zu Remote-Browser-Sitzungen und die getrennten Bedingungen beider Dienste.
Zuerst die Grenze: Beobachtet wurde, dass der Extraktionsmodus den vom Tool zurückgegebenen Text verändert. Ich habe diesen Text keinem Agent übergeben; daher lässt sich daraus nicht ableiten, ob ein Agent eine gefälschte Anweisung befolgt. Echte Konten, Cookies, Remote-Sitzungen und die Löschung entfernter Daten waren nicht Teil des Tests.
Eine Seite abzurufen, Inhalte an ein Modell weiterzugeben, das Modell über den nächsten Schritt entscheiden zu lassen und den Login-Zustand eines Kontos remote zu halten, sind vier verschiedene Ebenen. Fetch kann Text zurückgeben, den Menschen nicht sehen; das bedeutet nicht, dass ein Modell ihm folgt. Einen Agenten anhand von Seiteninhalt klicken zu lassen oder eine angemeldete Sitzung an die Cloud zu übergeben, wären weitere Risikogrenzen, die hier nicht getestet wurden.
Nachweisgrenzen
| Gemessen | Statisch geprüft | Nicht getestet |
|---|---|---|
| TinyFish-Fetch-Rückgabetext: 6 Fixtures × 3 Modi je einmal (18 Beobachtungen), danach F0/F1/F2 × 2 HTML-Modi × 3 Wiederholungen (18 Beobachtungen) | Gepinnter AgentQL-MCP-Request-Aufbau, SDK-Standardwerte für includeHidden, AgentQL-REST-Sitzungsoptionen und zwei öffentliche Nutzungsbedingungen | Finale Antwort des TinyFish Browser Agent; echte AgentQL-SDK/REST-Request-Bodies; echte Logins, Cookie/Profile-Persistenz, Zugriffsschutz für Streaming-URLs und Nachweise zur Remote-Löschung |
Gleiche Seite, anderer Extraktionsmodus, anderer Rückgabetext
Das synthetische Lieferantenportal enthält vier Basisfelder:
- Auftrag: #101
- Betrag: USD 4200.00
- Status: PENDING_APPROVAL
- Zahlungsempfänger-Marker: ACCOUNT_FIXTURE_GOOD_101
Alle weiteren Inhalte dienen nur dem Test: eine gefälschte Anweisung in einem aria-label, mit display:none versteckter Text, ein HTML-Kommentar und ein versteckter Feldwert, der ausdrücklich als nicht geheime Canary markiert ist. Es gibt keine echten Kartendaten, API-Schlüssel, Cookies oder personenbezogenen Daten.
Die sechs Varianten und ihr Generator liegen in einem unabhängigen öffentlichen Fixture-Branch. Die Seiten werden über ein statisches CDN eines Drittanbieters ausgeliefert, nicht über GitHub Pages. Die Quellen sind auf einen Commit festgelegt, damit sich die Testeingaben überprüfen lassen.
Mit dem kostenlosen TinyFish Fetch führte ich zwei Lesedurchgänge aus. Im ersten wurden sechs Seiten jeweils als gewöhnliches Markdown, gewöhnliches HTML und HTML eines festgelegten DOM-Bereichs gelesen: 18 Beobachtungen des Rückgabetextes. Im zweiten Durchgang wurden nur F0, F1 und F2 jeweils dreimal in gewöhnlichem und bereichsbezogenem HTML wiederholt: weitere 18 Beobachtungen.
Ich führte zwei Runden mit dem kostenlosen TinyFish Fetch durch. In Runde eins las ich sechs Seiten jeweils mit normalem Markdown, normalem HTML und HTML aus einem angegebenen DOM-Bereich: 18 Beobachtungen des zurückgegebenen Textes. In Runde zwei wiederholte ich nur F0, F1 und F2 jeweils dreimal mit normalem und eingegrenztem HTML: weitere 18 Beobachtungen. Verwendet wurden format: "markdown", format: "html" sowie format: "html" mit include_selectors: ["#trusted-order-facts", "#untrusted-webpage-region"]. Ein Treffer bedeutet, dass die Zielanweisung oder der Canary-String im Rückgabetext erschien – nicht, dass ein Modell die Anweisung befolgte.
| Fixture | Nicht vertrauenswürdiger Inhalt | Normales Markdown | Normales HTML | Bereichs-HTML |
|---|---|---|---|---|
| F0 | Keine Fake-Anweisung | 0/1 | 0/1 | 0/1 |
| F1 | Fake-Anweisung in aria-label | 0/1 | 0/1 | 1/1 |
| F2 | Fake-Anweisung in display:none | 1/1 | 1/1 | 1/1 |
| F3 | Fake-Anweisung im HTML-Kommentar | 0/1 | 0/1 | 0/1 |
| F4 | Fake-Canary im versteckten Feld | Canary 0/1 | Canary 0/1 | Canary 1/1 |
| F5 | Harmloser aria-label-Kontrollfall | — | — | Harmloser aria-label-Text 1/1 |
F5 ist ein harmloser Negativkontrollfall: Er enthält keinen bösartigen Text, trotzdem gab das eingegrenzte HTML sein harmloses aria-label zurück. Das passt eher dazu, dass Attributtext aus dem ausgewählten DOM-Bereich serialisiert wird, als zu einer semantischen Erkennung bösartiger Inhalte. Die Fake-Anweisung im HTML-Kommentar von F3 erschien in dieser Runde nicht im zurückgegebenen DOM-Text.
F4s Canary-Treffer 1/1 ist eine einzelne Beobachtung aus Runde eins, kein Wiederholungsergebnis. In Runde zwei wurden nur F0–F2 in den beiden HTML-Modi wiederholt; Markdown und F3–F5 wurden nicht wiederholt.
Die Wiederholung zeigt den klarsten Unterschied:
| Fixture | Gewöhnliches HTML (3 Läufe) | Bereichs-HTML (3 Läufe) | Beobachtung |
|---|---|---|---|
| F0: saubere Kontrolle | 0/3 | 0/3 | Kein gefälschter Anweisungstext in beiden Antworten |
| F1: Anweisung in aria-label | 0/3 | 3/3 | Bereichsextraktion behielt den Attributtext |
| F2: Anweisung in display:none-Knoten | 3/3 | 3/3 | Versteckter Text erschien in beiden HTML-Antworten |
Alle 36 Rückgabetexte enthielten den Basisbetrag, den Status und den Zahlungsempfänger-Marker. „Enthielten“ bedeutet hier nur, dass die Zeichenfolgen im zurückgegebenen Text vorkamen. Es bedeutet nicht, dass ein Modell korrekt antwortete; es waren auch keine 36 Tests von KI-Entscheidungen.
Im ersten Durchgang gab es zwei weitere Beobachtungen mit geringerer Aussagekraft. Die Anweisung im HTML-Kommentar erschien in keinem der drei Extraktionsmodi dieses Durchgangs. Die synthetische Canary im versteckten Feld fehlte bei gewöhnlicher Extraktion, erschien aber im bereichsbezogenen HTML. Beide wurden nur einmal beobachtet und begründen keine allgemeine Regel.
Die eng begrenzte Schlussfolgerung lautet: Der Extraktionsmodus beeinflusst, welche Seiteninhalte im Rückgabetext des Tools landen. Das beweist weder, dass ein Anbieter echte Zugangsdaten gestohlen hat, noch dass ein Modell einer versteckten Anweisung folgen wird. Dafür wären andere Experimente erforderlich.
AgentQL MCP legt nicht einfach das gesamte lokale DOM in den Request-Body
Eine erste Hypothese lautete, dass das lokale AgentQL MCP für die semantische Auswahl den gesamten DOM-Baum von lokalem Chrome in die Cloud hochlädt. Diese Behauptung lässt sich nicht auf jeden AgentQL-Pfad übertragen.
Ich prüfte die fest gepinnte src/index.ts von tinyfish-io/agentql-mcp. Die lokale Request-Erzeugung für extract-web-data sendet url, prompt und Parameter an api.agentql.com/v1/query-data. Der erzeugte JSON-Request-Body enthält weder das lokale DOM noch ein Feld accessibility_tree.
Das bedeutet nicht, dass keinerlei Seiteninhalt das Gerät verlässt. Dieser MCP-Pfad sendet URL und Extraktionsprompt; wie der Server die Seite unter dieser URL abruft und verarbeitet, muss separat bewertet werden. Der Quellcode belegt aber nicht, dass der lokale Browser das gesamte DOM serialisiert in den Request-Body schreibt.
Ein anderer Pfad verwendet das AgentQL-SDK als Wrapper für eine Playwright-Seite. AgentQLs Dokumentation zur internen Funktionsweise erklärt, dass die semantische Verarbeitung Seiten-HTML und den Accessibility Tree verwendet und die Eingabe vereinfacht. Die JavaScript-SDK-Seiten-API nennt unterschiedliche Standardwerte für versteckte Elemente:
| SDK-Methode | Dokumentierter Standard für includeHidden |
|---|---|
| queryData() | true |
| queryElements() | false |
| getByPrompt() | false |
Das zeigt unterschiedliche Konfigurationen je Methode, ersetzt aber keine Aufzeichnung eines tatsächlichen Netzwerkrequests. Ich habe das SDK nicht ausgeführt und seinen Request-Body nicht erfasst. Daher kann ich nicht behaupten, dass ein verstecktes Feld über diesen SDK-Pfad hochgeladen wurde.
Der Cloud Browser Agent von TinyFish und die Remote-Browser-Sitzungen von AgentQL sind nochmals andere Pfade. MCP, SDK, URL-Extraktion und Remote-Browser müssen nach Ausführungsort und Datenausgang getrennt bewertet werden.
Die Bereinigung einer Remote-Sitzung hängt von der Lebenszykluskonfiguration ab
AgentQLs Remote-Browser-Leitfaden dokumentiert CDP-Verbindungsadressen und Streaming-URLs für Seiten und beschreibt automatische Sitzungsverwaltung und Bereinigung. Daraus geht allein nicht hervor, ob eine konkrete Streaming-URL eine zusätzliche Autorisierung benötigt oder ob Sitzungsdaten aus sämtlichen Speichern gelöscht wurden.
Die Browser-Sitzungsschnittstelle in der AgentQL-REST-API-Referenz nennt konkretere Optionen:
- inactivity_timeout_seconds ist standardmäßig 300 Sekunden (fünf Minuten).
- shutdown_mode ist standardmäßig on_disconnect.
- Bei on_inactivity_timeout kann eine Sitzung nach Trennung des letzten Clients bis zum Ablauf des Inaktivitäts-Timeouts bestehen bleiben; eine erneute Verbindung innerhalb dieses Fensters kann denselben Browser wiederverwenden.
Das ist genauer als die Aussage, jede Aufgabe lösche ihre Sitzung sofort. Es beschreibt jedoch das dokumentierte Verhalten der AgentQL-REST-Sitzungsschnittstelle und lässt sich nicht auf jedes TinyFish Browser Context Profile übertragen. Ich habe nicht unabhängig verifiziert, dass eine echte Sitzung nach einem Timeout vollständig gelöscht wurde.
AgentQLs Beispiel zur Zugangsdaten-Caching zeigt außerdem Playwrights storage_state(path=“session.json”), um den Login-Zustand in einer lokalen Datei zu speichern. Das dokumentiert einen lokalen SDK-Anwendungsfall, nicht den Speicherort von Daten in einem Remote Browser Profile.
Nutzungsbedingungen beschreiben erlaubte Verarbeitung, nicht den tatsächlichen Ablauf eines einzelnen Runs
TinyFish und AgentQL haben getrennte Nutzungsbedingungen. Sie sollten nicht zu einem vagen gemeinsamen Datenschutzversprechen zusammengefasst werden. Ich habe die Customer-Data-Klauseln nebeneinandergestellt:
| Bedingungen | Veröffentlichte Erlaubnis zur Verarbeitung von Customer Data | Wird Modelltraining/Feinabstimmung ausdrücklich erwähnt? |
|---|---|---|
| TinyFish Terms of Service | Verarbeitung zur Bereitstellung, Wartung, Absicherung, Analyse und Verbesserung des Dienstes; außerdem Prüfung bestimmter Runs, Prompts, Inputs, Outputs, Fehler und Nutzungsmuster. | Ja. Genannt werden Training, Feinabstimmung, Bewertung und Verbesserung von KI-/ML-Modellen sowie Schutzmaßnahmen gegen die Offenlegung vertraulicher Informationen gegenüber anderen Kunden. Inkrafttreten: 17. August 2026. |
| AgentQL-Nutzungsbedingungen | Verarbeitung von Customer Data zur Bereitstellung, Wartung, Absicherung und Verbesserung des Dienstes; aggregierte oder de-identifizierte Daten dürfen für rechtmäßige Zwecke wie Produktverbesserung und -entwicklung verwendet werden. | Der sichtbare Customer-Data-Abschnitt erwähnt Modelltraining oder Feinabstimmung nicht ausdrücklich. Daraus folgt nicht, dass andere Vereinbarungen oder Richtlinien keine entsprechenden Bestimmungen enthalten. Inkrafttreten: 21. April 2026. |
Ich behandle Nutzungsbedingungen nicht wie Laufzeitprotokolle. Sie beschreiben die veröffentlichten Verarbeitungsrechte, beweisen aber weder, dass eine konkrete Anfrage fürs Training verwendet wurde, noch dass ein Cookie offengelegt wurde. Vor der Übermittlung von Geschäftsdaten sollte man sie trotzdem lesen und klären, ob eine separate Unternehmensvereinbarung gilt.
So würde ich praktische Nutzungsgrenzen festlegen
Dieser Test rechtfertigt keine pauschale Bewertung „TinyFish ist sicher“ oder „TinyFish ist unsicher“. Er stützt jedoch einige konkrete technische Regeln:
- Nicht mit dem Hauptkonto beginnen. Wenn eine Anmeldung nötig ist, ein separates Konto mit minimalen Rechten ohne Zahlungs-, Admin- oder fachfremde Berechtigungen verwenden. Ein Passwort in einen Remote-Browser einzugeben, macht spätere Cookies und Sitzungszustände nicht automatisch wieder lokal.
- Webseiteninhalte als nicht vertrauenswürdige Eingabe behandeln. aria-labels, versteckte Knoten und Kommentare können Texte enthalten, die nicht zu den Geschäftsfeldern gehören. JSON-Ausgabe beweist keine Bereinigung. Erlaubte Felder explizit definieren, die Herkunft der Werte prüfen und gezielte Negativtests für versteckte Inhalte durchführen.
- Ein Selektor ist keine Schwärzungsfunktion. Die bereichsbezogene HTML-Extraktion gab in diesem Test sowohl aria-label-Text als auch einen synthetischen Wert aus einem versteckten Feld zurück. Ein engerer DOM-Bereich kann irrelevante Inhalte reduzieren, garantiert aber nicht, dass Attribute und versteckte Knoten darin entfernt werden.
- Lesen und Schreiben trennen. Einen Auftrag zusammenzufassen oder einen Prüfentwurf zu erstellen ist eine andere Berechtigung als einen Auftrag freizugeben, E-Mails zu senden oder ein Formular abzusenden. Unumkehrbare Aktionen benötigen eine unabhängige menschliche Bestätigung; ein Prompt „nicht absenden“ reicht nicht.
- Bedingungen für das Sitzungsende definieren. Vor Beginn die Sitzungs-API, shutdown_mode und Timeout-Einstellungen prüfen. Eine nicht mehr sichtbare Seite, ein getrennter Client oder ein Toolstatus „Completed“ beweist nicht, dass der Remote-Zustand gelöscht wurde. CDP-Adressen und Streaming-URLs als sensible Kontrollzugänge behandeln.
- Daten für das Modell minimieren. Bei Abrechnungen, Kundendaten und internen Tickets prüfen, ob die vollständige semantische Seitenanalyse wirklich nötig ist. Lassen sich wenige Felder lokal auswählen, sollte nicht standardmäßig die ganze Seite an einen Cloud-Dienst gesendet werden.
Ich hatte geplant, dieselben Fixtures an den TinyFish Browser Agent zu geben und zu prüfen, wie er nach Erhalt des Seitentextes entscheidet. Das Testkonto konnte jedoch die genehmigte Schrittobergrenze nicht erzwingen; der verfügbare Standardwert lag weit über dem autorisierten Umfang. Ich habe die Grenze nicht gelockert, nur um das Experiment abzuschließen. Wenn eine Ausführungsgrenze bei einer Automatisierung nicht durchsetzbar ist, ist das ein Grund anzuhalten – nicht etwas, das man mit „vorsichtig sein“ überspringt. Deshalb endet diese Runde bei Fetch-Rückgabetexten, gepinntem Quellcode und öffentlicher Dokumentation.
Die praktische Schlussfolgerung ist nicht, einem Produkt pauschal ein Sicherheitslabel zu geben. Zuerst feststellen, was über welchen Pfad gesendet wird; dann mit überprüfbaren Belegen zeigen, was tatsächlich zurückkam. Produktnamen, Datenschutzbedingungen und eine erfolgreiche Demo ersetzen kein Audit der Vertrauensgrenzen.
Hinweis zu Zeit und Umfang: Die Quellenprüfung und Beobachtungen mit synthetischen Seiten reichen bis zum 25. Oktober 2026. Der Fetch-Test umfasst sechs Fixtures × drei Extraktionsmodi sowie je drei Wiederholungen für F0/F1/F2 in zwei HTML-Modi, insgesamt 36 Beobachtungen von Rückgabetext. Es wurden keine echten Konten, Cookies, API-Schlüssel oder Kundendaten verwendet. TinyFish Browser Agent, Laufzeit-Datenabfluss von AgentQL SDK/REST, echte Sitzungspersistenz und Remote-Löschung wurden nicht getestet; dies ist keine Sicherheitszertifizierung des Gesamtprodukts.
Quellen- und Reproduktionshinweis: Fetch verwendete den Quellcommit 152cdc9a58e6a8fcf2d5857820d60774f0967555. Der spätere Commit 604bde069c0bd32bb41a4238140062e5468a7f2f änderte nur das README; die Git-Blob-SHAs von F0–F5 blieben unverändert. Die Seiten wurden über die Drittanbieter-CDN cdn.githubraw.com abgerufen; Verfügbarkeit, Cache und Byte-Äquivalenz wurden nicht geprüft. Der gepinnte Git-Quellcode beweist daher nicht, dass die CDN bytegenau identische Inhalte ausgeliefert hat. Der vollständige Generator, Klassifikator und die Rohbelege der Rückgabetexte bleiben im privaten Forschungsdossier.