How to check global privacy control
Global Privacy Control is one bit sent two ways: the Sec-GPC: 1 request header and the navigator.globalPrivacyControl property. Point the browser under test at a server that echoes both, then compare them. Brave 1.95 reported true and sent the header. Chrome 152 sent neither.
Why check this
Run this before a release that touches opt-out handling, and again in regression whenever the consent layer changes. The signal is not decoration: the California Attorney General's CCPA FAQ says of GPC that "Under law, it must be honored by covered businesses as a valid consumer request to stop the sale or sharing of personal information."
The failure this catches is a one-sided implementation. Server code reads Sec-GPC and the front end reads navigator.globalPrivacyControl, and nobody checks that a real browser sets both. A visitor whose extension injects only the header then opts out on the wire while the page script sees nothing, so the opt-out branch never runs and the request is logged as consent.
Prerequisites
- Node 22 and
puppeteer-core. It ships no browser, so it drives the Chrome and Brave already installed. - curl 8.21 or later. The syntax of the header is fixed by the GPC specification, section 3:
Sec-GPC-field-value = "1". - An echo server on port 9641 that prints the two privacy headers it received. Save as
echo.mjs:
import { createServer } from 'node:http';
const WATCH = ['sec-gpc', 'dnt', 'user-agent'];
createServer((req, res) => {
const seen = WATCH.map((h) => `${h}: ${req.headers[h] ?? '(absent)'}`).join('\n');
console.log(`${req.method} ${req.url}\n${seen}\n`);
res.setHeader('content-type', 'text/html; charset=utf-8');
res.end(`<!doctype html><title>echo</title><pre>${seen}</pre>`);
}).listen(9641, '127.0.0.1', () => console.log('echo listening on http://127.0.0.1:9641/'));
- A probe that loads that page in a named browser and reads the property back. Save as
probe.mjs:
import { launch } from 'puppeteer-core';
const BIN = {
chrome: 'C:/Program Files/Google/Chrome/Application/chrome.exe',
brave: 'C:/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe',
};
const which = process.argv[2];
const browser = await launch({ executablePath: BIN[which], headless: true });
const page = await browser.newPage();
if (process.argv.includes('--inject')) {
const cdp = await page.createCDPSession();
await cdp.send('Network.enable');
await cdp.send('Network.setExtraHTTPHeaders', { headers: { 'Sec-GPC': '1' } });
}
await page.goto('http://127.0.0.1:9641/');
const r = await page.evaluate(() => ({
value: navigator.globalPrivacyControl,
present: 'globalPrivacyControl' in navigator,
echoed: document.querySelector('pre').textContent,
}));
console.log(`${which} ${await browser.version()}`);
console.log(`navigator.globalPrivacyControl: ${r.value} (in navigator: ${r.present})`);
console.log(`server saw:\n${r.echoed}`);
await browser.close();
- The browser figures below are one capture on one machine on 2026-09-12. Another build or another profile will report something else, which is the reason to measure rather than assume.
Steps
- Step 1.
Send the header by hand, so the server side can be tested without a browser at all.
curl -s -H 'Sec-GPC: 1' http://127.0.0.1:9641/<!doctype html><title>echo</title><pre>sec-gpc: 1 dnt: (absent) user-agent: curl/8.21.0</pre>One header, one value. Any other value is outside the specification.
- Step 2.
Read what the default browser does. Nothing is enabled and no extension is loaded.
node probe.mjs chromechrome Chrome/152.0.7977.76 navigator.globalPrivacyControl: undefined (in navigator: false) server saw: sec-gpc: (absent) dnt: (absent) user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/152.0.0.0 Safari/537.36in navigator: falseis the line that matters. The property is not defined and set to nothing, it is absent from the object. - Step 3.
Read a browser that ships the signal switched on.
node probe.mjs bravebrave Chrome/153.0.8010.37 navigator.globalPrivacyControl: true (in navigator: true) server saw: sec-gpc: 1 dnt: (absent) user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/153.0.0.0 Safari/537.36Both forms agree, which is what the specification describes: the property "enables a client-side script to determine what Sec-GPC header field value was sent".
- Step 4.
Inject the header into Chrome the way a proxy or an extension does, and read the property again.
node probe.mjs chrome --injectchrome Chrome/152.0.7977.76 navigator.globalPrivacyControl: undefined (in navigator: false) server saw: sec-gpc: 1 dnt: (absent) user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/152.0.0.0 Safari/537.36The two forms now disagree. The server sees an opt-out and the page script sees nothing.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| Header 1 and property true | The browser sends the signal natively | Test both code paths against this browser. |
| Header absent, property absent | The browser has no GPC support | A missing property is not the same as an opt-out declined. |
| Header 1, property undefined | Something on the network path added the header | Read the header on the server, not the property in the page. |
| Header absent, property true | A script or extension set the property only | Server code will never see the opt-out. Treat as a defect. |
Common mistakes
What to check next
- How to check if a site honors global privacy control: the signal matters only if some behaviour changes, and this is how to measure the change.
- How to check do not track: the older header of the same shape, and what it is worth reading today.
- How to check if cookies are set without consent: the cookies that appear before any signal is honoured.
- How to check cookies on a website: the full cookie inventory to diff against.
FAQ
What is a GPC signal?
One bit that says the visitor opts out of the sale or sharing of their personal data. It travels as the request header Sec-GPC: 1 and, in browsers that support it natively, as the boolean navigator.globalPrivacyControl.
How do I enable global privacy control?
Use a browser that sends it. Brave sent it with no configuration in the capture above. Chrome 152 has no setting for it, so testers add an extension or a proxy, which produces the header without the property.
Which browsers send the signal?
Measure rather than trust a list, since support moves between versions. Run probe.mjs against each browser on the machine and record the version alongside the result, as the Verified block below does.
Is GPC part of a web standard?
The Global Privacy Control specification is a W3C Working Draft, dated 11 June 2026. Its own status section says it is "inappropriate to cite this document as other than a work in progress". Legal obligations attach to the signal separately from its standards status.
Verified
Verified by Maks Vernycurl 8.21.0Node 22.23.2Chrome 152.0.7977.76Brave 1.95.101
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic8 minpublished updated Maks Verny