2022年8月24日,加州总检察长办公室宣布与丝芙兰(Sephora)达成120万美元和解。指控很直接:丝芙兰出售了顾客的个人信息却没有告知,并且没有处理用户通过全局隐私控制(Global Privacy Control,GPC)等浏览器信号发出的退出请求。同一天,该办公室表示已就同样的GPC退出请求处理失败问题,向多家其他企业发出通知。
![]()
整个过程里,丝芙兰没有任何人需要点一下按钮。购物者的浏览器发出了一个信号,网站没有响应,广告标签继续加载。将近三年后,2025年7月,同一办公室宣布与Healthline.com达成155万美元和解,当时称之为迄今最大的CCPA和解。其中一项指控是,即便对已选择退出的消费者,Healthline仍与部分广告第三方共享数据;公告明确写道,企业必须遵守退出请求,包括通过GPC发出的请求。
如果你的站点上挂着Meta Pixel、TikTok Pixel或LinkedIn Insight Tag,这既是法律问题,也是前端问题。下面拆解这个信号到底是什么、在代码里“遵守”意味着什么,以及如何用一个开源命令行工具gpc-warden在CI里堵住漏洞。
GPC到底发送了什么
GPC是一项W3C规范。用户开启后,浏览器做两件事:在发出的请求中加上Sec-GPC: 1请求头;暴露navigator.globalPrivacyControl,一个只读布尔值,告诉脚本当前顶层页面是否发送了该请求头。
规范还定义了位于/.well-known/gpc.json的可选支持资源。其中gpc成员必须为true(服务器打算遵守GPC请求,至少在法律要求范围内)或false,可选的lastUpdate成员存放YYYY-MM-DD格式的日期。一个合法文件长这样:
{ "gpc": true, "lastUpdate": "2026-10-20" }
加州CCPA指南列出了退出出售或共享个人信息的权利,“包括通过GPC”,并告知消费者可以通过用户启用的全局隐私控制提交退出请求。所以这个请求头不是暗示,按该指南,它是你的站点应当响应的请求。
为什么站点会做错
读一个布尔值很简单。难的是大多数广告标签被直接以裸script标签粘进布局里。这样加载的标签在每次页面浏览时都会运行,你的代码根本没有机会在像素触发前问一句“这位访客退出过吗”。
在静态或准静态站点上遵守GPC,归结为两件事:
- 必须有代码读取信号:内联脚本、自己JS包里的加载器、检查Sec-GPC的边缘或Serverless函数,或者配置为尊重该信号的同意管理平台(CMP)。
- 每个出售/共享标签都必须以代码能拦截的方式加载:通过该加载器,或打上标记由CMP控制(例如type="text/plain"加分类属性)。
两者缺一,信号实际上就被忽略了,哪怕隐私政策上写着相反的话。
用gpc-warden检查构建产物
gpc-warden是一个零依赖的Node.js命令行工具、库和GitHub Action,检查构建输出的文件夹(Astro、Next.js的output: 'export'、Eleventy、Hugo、Jekyll、Docusaurus、VitePress或纯HTML)。它离线运行,检查四项:构建中是否有内联脚本、本地JS文件或边缘/Serverless函数读取navigator.globalPrivacyControl或Sec-GPC;出售或共享数据的广告供应商(Meta、Google Ads、TikTok、LinkedIn、X、Pinterest、Snap、Microsoft、Criteo、Taboola等)是否无条件加载;/.well-known/gpc.json是否存在、合法,且没有在存在出售/共享标签时写false;隐私政策是否提到GPC或退出偏好信号。
它需要Node.js 18或更新版本,基本用法是npx gpc-warden dist,也可以加--fail-on medium,或用--format sarif输出报告。对一个演示小店运行时,首页直接粘了Meta和TikTok像素、没有gpc.json、隐私页只谈“Do Not Sell”链接,结果是三条高危发现让运行以退出码1失败:没有代码读取信号,每个像素都无条件加载。两条低危发现指向缺失的gpc.json和政策措辞。每条发现都附带具体修复建议。
修复演示站点
修复办法是一个小加载器,只在浏览器没有发送GPC退出信号时才注入出售/共享标签:
if (navigator.globalPrivacyControl !== true) { ...逐个创建script并append到head... }
页面改为引入这个加载器而不是两个像素,构建发布/.well-known/gpc.json,隐私政策写明GPC信号被视为退出出售和共享的请求。真实站点还应结合同意逻辑,并在设置Cookie或转发数据的服务端读取Sec-GPC。重新运行检查,输出显示0个出售/共享供应商、在js/ads.js中找到GPC处理、无发现、通过。
如果你用OneTrust、Osano、Usercentrics、Didomi、Termly、Ketch、TrustArc或Transcend这类CMP,gpc-warden能识别出来,但看不到你的后台设置。在CMP里开启GPC支持后,传--cmp-honours-gpc(或在配置里设"cmpHonoursGpc": true)来确认。
放进CI
仓库自带一个GitHub Action,无需安装步骤即可运行内置CLI。先构建站点,再把action指向输出文件夹,配置path、fail-on、sarif等参数,并可接上SARIF上传步骤。默认它还会在任务摘要里加一份Markdown报告。退出码0为通过,1为达到或超过--fail-on的发现,2为用法或配置错误,因此在任何其他CI系统里用一句npx gpc-warden dist也能同样工作。
对于确实不需要检查的页面,配置文件支持带时限的允许列表。每个条目需要理由,一旦expires日期过去,发现会重新出现,并附带一条allowlist-expired发现。
它还提供小型库API,方便把检查接进自己的工具链:导入check和validateGpcJson,对构建目录跑检查,或校验一段gpc.json内容。
它做不到什么
gpc-warden是对构建产物的静态检查。它无法证明某个标签在运行时真的被抑制,不审计同意横幅(姊妹工具consent-warden负责),不构建完整的追踪器清单(tracker-warden负责),也不构成法律建议。它做的是抓住最常见、最机械的错误:一个广告标签躺在HTML里,任何GPC检查都够不着它。
加州监管者已经把被忽略的GPC信号当作被忽略的退出请求处理,先是丝芙兰,后是Healthline。规范给了你清晰的请求头、清晰的JavaScript属性和清晰的声明文件。把出售/共享标签挡在信号之后,发布gpc.json,在政策里写明,然后让CI在像素偷偷溜回来时把构建打回去。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.