Independent AI intelligence Two editions daily · ET
Fervor AI

Analysis · September 20, 2026 · concept

OpenAI measurement pixelprivacyagent-infrastructure

OpenAI's Measurement Pixel Leaks Your Visitors Before Its Own Privacy Code Runs

A tracking cookie reaches the vendor at script-load time, before a single line of the SDK executes. The fix is one HTML attribute, and it has a real cost.

Buried in a September 20 teardown of OpenAI's advertising collector is a sentence that should bother anyone who has ever embedded a third-party tag. The pixel SDK has a code path that omits credentials. It does not help. The browser attaches cookies to the <script src> request that loads the SDK before any of OpenAI's code runs.

Sit with that for a second. The vendor shipped a no-credentials option. The option is real. It is also irrelevant to the thing it appears to protect, because by the time that option exists as executable JavaScript, the request carrying the identifier has already completed. You cannot write a privacy control in a file that has to be fetched before the control can run.

This is not an OpenAI bug. It is how the web platform has always worked, and it is the part of third-party tag integration that almost nobody audits.

What the collector actually does

The mechanism, as documented by the researcher at Buchodi's Threat Intel who reproduced it on his own device, runs in three steps.

On chatgpt.com, the client generates 16 random bytes and calls POST /backend-api/bazaar/obi/sync-token, or the /backend-anon/ variant when you are signed out. The backend returns an RS256 JWT with aud: bzr.openai.com, purpose: obi_sync, a sub naming your account subject, the 22-character obi identifier, and a 60-second expiry. The token binds the identifier to the account.

The client then POSTs that token cross-site to bzr.openai.com/v1/obi/sync, which responds with a cookie:

Set-Cookie: __obi=«value»; Domain=.openai.com; HttpOnly;
            Max-Age=31536000; Path=/; SameSite=none; Secure

SameSite=none with Secure is exactly the configuration a cookie needs in order to be sent on cross-site requests. One year lifetime. The obi value inside the JWT and the value in the cookie are identical.

Step three is where your site comes in. Any company buying ChatGPT ads installs OpenAI's measurement pixel, the same way it already runs Meta and Google tags. When a visitor with __obi in the jar loads your page, three request classes go to OpenAI's hosts, and in the researcher's testing all three carried the cookie: the GET for bzrcdn.openai.com/sdk/oaiq.min.js, a POST to bzr.openai.com/v1/sdk/events with a conversion reference, and a POST to the same endpoint on what the SDK internally calls its no-credentials path.

The first row is the one that matters. It is not an event. It is the script tag.

Why "omit credentials" cannot cover a script tag

Fetch-level credential controls apply to fetches your code makes. fetch(url, {credentials: 'omit'}) governs a request originated by JavaScript that is already parsed, compiled and executing. The request that delivered that JavaScript happened earlier, initiated by the HTML parser, governed by the rules for subresource loads.

By default, when a <script> element carries no crossorigin attribute, MDN's reference is explicit that CORS is not used at all for that element. The browser follows ordinary cookie rules for a cross-site subresource, which is precisely the case SameSite=none; Secure was designed to satisfy. The cookie goes.

Nothing in the SDK can prevent this, because the SDK is the payload of the request that discloses it. Loading the tag is the disclosure.

The control that does work

There is a fix, and it lives in your HTML, not the vendor's JavaScript. The crossorigin attribute on <script> is a CORS settings attribute, and MDN spells out what each value does:

anonymous: Request uses CORS headers and credentials flag is set to 'same-origin'. There is no exchange of user credentials via cookies, client-side TLS certificates or HTTP authentication, unless destination is the same origin.

So:

<script src="https://bzrcdn.openai.com/sdk/oaiq.min.js"
        crossorigin="anonymous"></script>

That sets the credentials flag to same-origin for a cross-origin destination, which means no cookies on the script load. The identifier stays out of that request.

Now the honest part, because this is where most write-ups would stop and leave you with a broken page.

Adding crossorigin="anonymous" switches the request into CORS mode. The server must then return an Access-Control-Allow-Origin header that permits your origin, or the browser rejects the response and the script does not execute at all. A CDN serving a tracking SDK may or may not send permissive CORS headers. If it does not, your pixel silently stops working, which for an advertiser means conversion reporting goes dark and nobody notices for a week.

Test it in staging, watch the network panel for the CORS failure, and decide deliberately. The attribute is not a free win. It is a trade you should make on purpose rather than discover by accident.

Put this into practice

Three things, in increasing order of effort, that any team running third-party tags can do this week.

Inventory your tags by request, not by vendor. Open DevTools on a page with your tags live, filter the network panel by the vendor's domains, and look at the Cookies column on every request, including the script loads themselves. You are looking for anything with a third-party cookie attached to a subresource fetch. This takes twenty minutes and it is the only way to see what the vendor's documentation does not say.

Check what the SDK scrapes, separately from what you pass it. The same teardown found the pixel labels its identity sources: in for values the advertiser deliberately passes, and fm, ht, js for values scraped from form fields, rendered page text and the tag-manager bus. In observed traffic, scraped identity outnumbered advertiser-supplied identity 685 events to 255. The SDK replaces window.dataLayer.push with its own function, also reads adobeDataLayer, and finds renamed GTM containers by parsing the l= parameter off the gtm.js script tag. If you believed you controlled the data flow by controlling what you pass, check that belief against a capture.

Decide your crossorigin position per tag, and write it down. For each third-party script, answer two questions: does it need credentials to function, and does its host send CORS headers? Most analytics and conversion tags do not need cookies on the script load to do their stated job. Test crossorigin="anonymous" on each, keep the ones that survive, and document why you kept the exceptions. A Content-Security-Policy with a tight script-src is a good companion here, though it restricts where scripts come from rather than what credentials ride along.

For your own visitors, a Subresource Integrity integrity attribute is worth adding at the same time, since you are already editing the tag. It does not touch the cookie problem at all. It does mean a swapped SDK file will not execute.

What I think this means, and where I could be wrong

My position is that the classification argument, which is where most of the coverage went, is the less interesting half of this story.

OpenAI's cookie policy, last updated September 10, 2026, lists __obi as the single entry in its Analytics cookies table, one year, on chatgpt.com and openai.com. OpenAI runs oai_consent_analytics and oai_consent_marketing as separate choices, and every sync token the researcher decoded carried consent_decision: analytics_allowed. A user who granted analytics and refused marketing gets this. That is a genuine complaint and I would want an answer to it too. He emailed press@openai.com and privacy@openai.com on September 14 asking exactly that; Support acknowledged and answered neither question.

But the classification is a policy artifact. It can be changed with an edit to a table. The script-load disclosure is structural, it applies to every third-party tag you have ever pasted into a template, and it will still be true after this particular cookie is reclassified, renamed, or retired.

Here is what would change my mind or sharpen the claim, and what is not established:

The researcher states his own limits and they matter. This was observed on Chrome for Android. Safari's Intelligent Tracking Prevention blocks all third-party cookies and Chrome on iOS runs on WebKit, so the mechanism does not operate on any iOS browser; desktop Chrome was untested. Roughly one ChatGPT session in five produced a sync token at all. And the server-side join, OpenAI resolving __obi back to your account, is inferred from the design rather than observed. A 202 means the collector accepted the event with the cookie attached. He says plainly that he did not watch the join happen, and I am not going to assert it on his behalf.

I also have not verified the crossorigin="anonymous" behaviour against OpenAI's specific CDN. The browser behaviour is specified and documented; whether bzrcdn.openai.com returns usable CORS headers is an empirical question about one host, and you should answer it for yourself before shipping the attribute to production.

The part I hold firmly: a privacy control that ships inside a fetched script cannot protect the fetch that delivered it. That is not a claim about OpenAI. It is a claim about load order, and load order does not negotiate.

What to do with this

You probably run third-party tags. Most teams do, and most inherited them from somebody who left. Go look at what your script loads carry, before you form an opinion about anyone's cookie table. The audit is cheap, the finding is yours, and unlike a policy page it will tell you the truth about your own site.

Sources: Buchodi's Threat Intel, September 20, 2026 · OpenAI cookie policy · OpenAI measurement pixel docs · MDN, crossorigin HTML attribute


Medium metadata

Title: OpenAI's Measurement Pixel Leaks Your Visitors Before Its Own Privacy Code Runs Subtitle: A tracking cookie reaches the vendor at script-load time, before a single line of the SDK executes. The fix is one HTML attribute, and it has a real cost. Tags: Privacy, Web Development, OpenAI, JavaScript, Security Canonical: publish on fervorai.dev first, then import to Medium from that URL.