Op 20 september publiceerde een onderzoeker een grondige analyse van een cookie met de naam __obi. Diezelfde dag stond de analyse op de voorpagina van Hacker News, en de reacties vielen in twee kampen uiteen. Het ene kamp was boos dat OpenAI ChatGPT-gebruikers over het hele web volgt. Het andere haalde de schouders op: Facebook en Google doen dit al ruim tien jaar.
Allebei hebben ze gelijk, en allebei gaan ze voorbij aan het deel waar jij als developer iets aan kunt doen.
De cookie reist alleen mee omdat er een script van bzrcdn.openai.com laadt op de pagina van een webshop. Dat script laadt omdat iemand een snippet in een <head>-tag heeft geplakt. De documentatie van OpenAI vraagt je de snippet "near the top of your <head>" te zetten, op elke pagina waar je conversies wilt meten.
Die iemand is een developer. Misschien jij wel.
Wat de snippet doet
De analyse op buchodi.com is zorgvuldig werk. De onderzoeker bootst het mechanisme na op een echte telefoon, controleert het met twee losse meetmethoden en legt het naast maanden aan waargenomen verkeer van 936 advertentiepixels. Zo werkt het, stap voor stap.
Open je ChatGPT, dan vraagt de client de backend van OpenAI om een kortlevend, ondertekend token. Dat token koppelt je account aan een willekeurige identifier. Daarna stuurt de client het naar bzr.openai.com, de advertentiecollector van OpenAI, en die antwoordt met een cookie:
Set-Cookie: __obi=«redacted»; Domain=.openai.com; HttpOnly;
Max-Age=31536000; Path=/; SameSite=none; SecureEen jaar geldig, en SameSite=none. Die instelling bestaat om één reden: de browser moet de cookie meesturen bij requests vanaf andere sites. Alle andere cookies van OpenAI die de onderzoeker bekeek, werden bij die requests tegengehouden. __obi was de enige die zo was ingesteld dat hij naar andere sites mee mocht.
Dan bezoek je een webshop die op ChatGPT adverteert. De pagina laadt de measurement pixel van OpenAI, de browser hangt __obi aan die request, en OpenAI krijgt een identifier die aan je ChatGPT-account vastzit, samen met de pagina die je bekijkt. Op de eigen telefoon van de onderzoeker ging één __obi-waarde de deur uit vanaf 12 commerciële sites, waaronder Chewy, Wayfair, Coursera en HelloFresh. Uitgelogd werkte het ook: 196 van de 932 gedecodeerde tokens hadden een anoniem subject, dat per apparaat minstens 27 dagen gelijk bleef.
Er is echt iets voor de pixel te zeggen
Er is hier niets exotisch aan de hand. Meta draait al jaren precies dezelfde constructie: een ingelogd account, een third-party cookie op de pixel, conversies op andere sites teruggekoppeld aan een profiel. Koop je advertenties, dan wil je weten welke iets verkochten. Een conversiepixel is het standaardantwoord van de branche, en OpenAI bouwde de pixel die zijn adverteerders verwachtten.
De onderzoeker is ook open over de grenzen. Het is waargenomen op Chrome voor Android, en Chrome op de desktop is niet getest. Op iOS speelt het niet: Safari blokkeert third-party cookies, en elke browser op iOS draait op dezelfde WebKit-engine.
Het werkt ook lang niet altijd. Slechts ongeveer één op de vijf ChatGPT-sessies leverde überhaupt een sync-token op. En de laatste stap, dat OpenAI het event op zijn servers aan een account koppelt, volgt uit het ontwerp maar is niet rechtstreeks waargenomen.
Het mechanisme zelf is gewone adtech. Nieuw is waar de identiteit vandaan komt. Mensen vertellen een chatbot dingen die ze nooit op een sociaal netwerk zouden zetten, en het account met die gesprekken wordt nu de sleutel die hun aankopen, hun leesgedrag en hun zoektochten naar schuldhulp aan elkaar knoopt.
Wat de documentatie niet vertelt
Hier wordt het een probleem voor developers, en dat begint bij de documentatie van de measurement pixel van OpenAI zelf.
De documentatie noemt __obi nergens. Ze somt de cookies op die de pixel op jouw domein zet, __oppref en __obref, met hun levensduur. De cookie die je bezoeker bij OpenAI identificeert, staat niet op de pagina waarmee je de pixel installeerde.
De onderzoeker komt langs een andere weg op hetzelfde uit. De cookie staat op een domein dat geen script van een adverteerder kan lezen, en de conclusie in de analyse luidt: "They installed a conversion pixel and have no way to know their visitors are being resolved to a ChatGPT identity."
Consent staat aan tot je hem uitzet. Letterlijk uit de documentatie: "The Pixel initializes consent to true by default unless you set it to false or the Pixel finds a stored denial."
En uitzetten komt te laat. De gedocumenteerde manier om op toestemming te wachten is oaiq("consent", false) vóór init. Dan verstuurt de SDK geen meetevents. Aan de request waarmee de browser de SDK ophaalt, verandert dat niets, en juist bij die request zag de onderzoeker __obi meereizen. De browser stuurt de cookie mee nog voordat er ook maar één regel code van OpenAI draait. Het laden van de tag is genoeg.
Ik wilde dat zien zonder dat er een ChatGPT-account aan te pas kwam. Dus bouwde ik twee lokale pagina's en opende ze in een standaardprofiel van Chromium 153, dat third-party cookies toestaat. De browser had een nagemaakte __obi-cookie op .openai.com, en de hostnamen van OpenAI wezen naar een lokale server, zodat er niets bij OpenAI aankwam.
Pagina één is de gedocumenteerde snippet met oaiq("consent", false) vóór init. Pagina twee voegt dezelfde loader pas in na een klik op een akkoordknop, en die klik komt in de test nooit. Ingekorte output van het script:
page one GET /sdk/oaiq.min.js Cookie: __obi=local-test-value
page two (no requests to openai.com)De consent-flag stond al op false voordat de SDK bestond, en de cookie ging toch mee. Alleen de pagina die het script nooit laadde, hield hem binnen. Firefox en Safari, die third-party cookies afschermen of blokkeren, hadden hem op geen van beide pagina's meegestuurd.
Dan is er nog automatic advanced matching, en daar spreken het verkeer van de onderzoeker en de documentatie van OpenAI elkaar tegen. De documentatie belooft: "Raw customer information is not sent to OpenAI through automatic advanced matching." In het waargenomen verkeer leest de SDK formuliervelden en zichtbare paginatekst, en neemt hij window.dataLayer.push over om e-mailadressen en telefoonnummers uit je tag manager te vissen. Bij 638 van de 881 pixels waarvan de instelling bekend was, stond de functie aan.
E-mail, telefoon en namen worden met SHA-256 gehasht voordat ze vertrekken. Land, regio, plaats en postcode gaan gewoon leesbaar mee. URL's worden ingekort tot origin plus pad, en onder de paden die bij de collector aankwamen zaten een medische aandoening, een aanvraagtraject voor schuldhulp en een intakeformulier voor een rechtszaak.
Je slugs zijn data. Je koos ze voor zoekmachines, en nu beschrijven ze je bezoekers aan een advertentienetwerk.
Hoe OpenAI het zelf noemt
Het cookiebeleid van OpenAI zet __obi onder Analytics cookies, als enige in die sectie, met als omschrijving: "These cookies help us understand how our Services perform and are used". Voor marketing is er een aparte toestemmingskeuze. Elk sync-token dat de onderzoeker decodeerde, droeg consent_decision: analytics_allowed. Wie analytics toestaat en marketing weigert, krijgt dus toch de identifier die over sites heen gaat.
De onderzoeker legde dit op 14 september voor aan het pers- en het privacyadres van OpenAI. Support bevestigde de ontvangst van de mail en gaf verder geen antwoord.
Wat je wel kunt doen
Draait jouw site deze pixel, of vroeg je marketingteam je onlangs om hem toe te voegen, dan ligt de oplossing bij jou.
- Zet het script zelf op slot. Voeg de loader pas in als de bezoeker echt toestemming heeft gegeven. Een consent-flag in een script dat al geladen is, beschermt niets, want het lek zat al in het laden. Dat is pagina twee uit de test hierboven.
- Zet automatic advanced matching uit. Het is een instelling in de Ads Manager van OpenAI. Of een script je formulieren mag uitlezen, hoort iemand in je team bewust te beslissen.
- Stuur conversies vanaf je server. OpenAI biedt een server-side Conversions API. Een request vanuit je backend bevat precies de velden die jij erin stopt, en geen enkele cookie van je bezoeker.
- Lees je CSP als inventarisatie. De documentatie vraagt je
bzrcdn.openai.comenbzr.openai.comtoe te voegen aan je Content Security Policy. Elke host in die header is een derde partij die je bezoekers ziet. Weet je niet waarom een host erin staat, zoek het dan uit.
Dit gaat verder dan één pixel. Een snippet van een derde partij is een dependency die je installeert zonder hem te lezen, en eerder schreef ik al dat de ongelezen dependency precies is waar de MCP-supply chain misgaat. Het is ook de les van de coding agent die een hele repository uploadde: wat een tool verstuurt, weet je pas als je naar het verkeer kijkt.
Deze site draait helemaal geen analytics, en dat heb ik als besluit vastgelegd. Het kost me elk cijfer dat ik misschien graag had gezien. Het betekent ook dat ik nooit via een thread op Hacker News hoef te ontdekken wat mijn eigen <head> heeft uitgespookt. Staan je gegevens toch bij een leverancier, dan begint de regie over je data terugnemen met weten waar ze naartoe gaan.
De titel van de analyse zegt dat OpenAI weet wat je op andere websites doet. Dat weet OpenAI omdat die websites het vertelden. Bij elk van die teams zit iemand die daar een eind aan kan maken.