<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ProtoConsent</title>
    <description>The latest articles on DEV Community by ProtoConsent (@protoconsent).</description>
    <link>https://dev.to/protoconsent</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898176%2Fb2dc60e1-b9de-4118-9716-2b26ace10f82.png</url>
      <title>DEV Community: ProtoConsent</title>
      <link>https://dev.to/protoconsent</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/protoconsent"/>
    <language>en</language>
    <item>
      <title>An open API for composable privacy extensions</title>
      <dc:creator>ProtoConsent</dc:creator>
      <pubDate>Sun, 26 Apr 2026 01:52:24 +0000</pubDate>
      <link>https://dev.to/protoconsent/an-open-api-for-composable-privacy-extensions-cgo</link>
      <guid>https://dev.to/protoconsent/an-open-api-for-composable-privacy-extensions-cgo</guid>
      <description>&lt;p&gt;&lt;em&gt;How browser extensions can query the user's consent state via a standard protocol&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz241onmr8njfl422jki0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz241onmr8njfl422jki0.png" alt="Inter-extension API log" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: privacy tools that can't talk to each other
&lt;/h2&gt;

&lt;p&gt;The browser extension ecosystem has ad blockers, cookie managers, fingerprint protectors, and VPN clients. Each makes decisions about user privacy independently. None of them knows what the others are doing, and none of them knows what the user actually wants at the purpose level.&lt;/p&gt;

&lt;p&gt;If an ad blocker wants to know whether the user has allowed analytics on a given site, there's no way to ask. If a cookie manager wants to check whether the user has denied ads, it has to implement its own consent model. Every privacy extension reinvents the same wheel, and the user has to configure each one separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  A read-only consent API
&lt;/h2&gt;

&lt;p&gt;ProtoConsent exposes an inter-extension API that lets other browser extensions query the user's consent state. The protocol uses &lt;code&gt;chrome.runtime.sendMessage&lt;/code&gt; with ProtoConsent's extension ID as the target. The API is read-only: consumer extensions can read preferences but never modify them.&lt;/p&gt;

&lt;p&gt;Two message types are supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capabilities discovery&lt;/strong&gt;: the consumer asks what the provider supports (protocol version, available purposes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent query&lt;/strong&gt;: the consumer asks for the user's consent state on a specific domain. The response contains all six purposes as booleans, plus the active profile name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A consent query looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Query&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;protoconsent:query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Response&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;protoconsent:response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;purposes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;functional&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;personalization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;third_parties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;advanced_tracking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balanced&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One domain per request. No bulk queries. The response contains only the fixed six-purpose schema, a profile name, and a version string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust on first use
&lt;/h2&gt;

&lt;p&gt;The API is disabled by default. The user must explicitly enable it in ProtoConsent's settings. Even then, each consumer extension must be individually approved.&lt;/p&gt;

&lt;p&gt;On first contact from an unknown extension, ProtoConsent stores a pending authorization request and responds with a &lt;code&gt;need_authorization&lt;/code&gt; error. The user can then approve or deny the extension from the settings UI. This is a &lt;strong&gt;TOFU (Trust on First Use)&lt;/strong&gt; model: no extension gets access without the user's explicit approval.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Approved extensions are stored in an allowlist.&lt;/li&gt;
&lt;li&gt;Denied extensions are moved to a denylist. Future messages from denied extensions are silently dropped, giving no signal that ProtoConsent is active.&lt;/li&gt;
&lt;li&gt;The pending queue is capped at 10 entries to prevent flooding.&lt;/li&gt;
&lt;li&gt;A global cooldown limits new unknown extension IDs to 3 per minute.&lt;/li&gt;
&lt;li&gt;Approved extensions are rate-limited to 10 requests per minute.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Privacy tools today are silos. Each one has its own model of what the user wants, its own configuration UI, and its own enforcement rules. The result is duplication, inconsistency, and cognitive load for the user.&lt;/p&gt;

&lt;p&gt;An open consent API changes this. A cookie manager could check whether the user has allowed analytics before deciding what to clean. A content blocker could query purpose preferences before applying its rules. A fingerprint protector could adapt its behavior based on whether the user has denied advanced tracking on a given site.&lt;/p&gt;

&lt;p&gt;The user configures their preferences once, in one place. Other tools read those preferences and adapt. That's composable privacy: tools that work together instead of side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security by design
&lt;/h2&gt;

&lt;p&gt;The protocol is designed to be safe by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-only&lt;/strong&gt;: there is no code path from external messages to storage writes or rule changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sender verification&lt;/strong&gt;: &lt;code&gt;sender.id&lt;/code&gt; is provided by the browser and cannot be forged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No intrusive prompts&lt;/strong&gt;: authorization requests are queued silently and reviewed at the user's discretion. No popup windows, no clickjacking vectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observable&lt;/strong&gt;: all inter-extension events (successes, errors, rate limits) are visible in the extension's log, timestamped and color-coded.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cross-browser compatibility
&lt;/h2&gt;

&lt;p&gt;The protocol works identically on Chrome and Firefox. Both support &lt;code&gt;chrome.runtime.onMessageExternal&lt;/code&gt; and runtime sender verification. ProtoConsent omits &lt;code&gt;externally_connectable&lt;/code&gt; from the manifest, giving open access on both browsers. Security is enforced at runtime (opt-in, allowlist, rate limiting), not at the manifest level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;A minimal test consumer extension is available in the &lt;a href="https://github.com/ProtoConsent/ProtoConsent" rel="noopener noreferrer"&gt;source repository&lt;/a&gt; under &lt;code&gt;examples/test-consumer/&lt;/code&gt;. It sends capabilities queries, consent queries, invalid inputs, and rate-limit tests, and logs responses in a popup panel.&lt;/p&gt;

&lt;p&gt;ProtoConsent is free and open source (GPL-3.0+). The inter-extension protocol is part of the draft specification and open to feedback.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How ProtoConsent answers consent banners without touching the DOM</title>
      <dc:creator>ProtoConsent</dc:creator>
      <pubDate>Sun, 26 Apr 2026 01:49:16 +0000</pubDate>
      <link>https://dev.to/protoconsent/how-protoconsent-answers-consent-banners-without-touching-the-dom-ie7</link>
      <guid>https://dev.to/protoconsent/how-protoconsent-answers-consent-banners-without-touching-the-dom-ie7</guid>
      <description>&lt;p&gt;&lt;em&gt;A declarative approach to CMP auto-response: cookie injection, not click simulation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vtx8nhue6bbj5azztsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vtx8nhue6bbj5azztsm.png" alt="Monitoring dashboard" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The banner problem
&lt;/h2&gt;

&lt;p&gt;Consent management platforms (CMPs) are used by millions of websites to show consent banners. When you visit a site, the CMP checks for a cookie that records your consent. If it doesn't find one, it shows the banner.&lt;/p&gt;

&lt;p&gt;Most tools that deal with banners take one of two approaches: they simulate clicks on banner buttons (fragile, depends on DOM structure) or they block the CMP script entirely (breaks sites that check &lt;code&gt;__tcfapi&lt;/code&gt; for vendor compliance). Both are reactive: they wait for something to appear, then act.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ProtoConsent approach: declare, don't interact
&lt;/h2&gt;

&lt;p&gt;ProtoConsent takes a different path. Instead of interacting with the banner after it appears, it writes the consent cookie that the CMP expects to find &lt;em&gt;before&lt;/em&gt; the CMP script even loads.&lt;/p&gt;

&lt;p&gt;When a page loads, a content script runs at &lt;code&gt;document_start&lt;/code&gt; (before any page JavaScript executes). It reads the user's purpose preferences and a set of CMP signature templates from extension storage. For each applicable CMP, it generates a valid consent cookie by replacing purpose placeholders with the user's choices and writes it to the document. When the CMP script loads a moment later, it reads its own cookie, sees a valid consent record, and skips the banner entirely.&lt;/p&gt;

&lt;p&gt;The user's preferences are enforced without ever touching the DOM, without simulating clicks, and without waiting for the banner to render.&lt;/p&gt;

&lt;h2&gt;
  
  
  CMP signatures
&lt;/h2&gt;

&lt;p&gt;Each supported CMP is described by a JSON signature that specifies the cookie name, a value template with purpose placeholders, and optional CSS selectors for the banner. A simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"template"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"groups=1:1,2:{analytics},3:{personalization},4:{ads}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#consent-banner"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content script replaces &lt;code&gt;{analytics}&lt;/code&gt;, &lt;code&gt;{personalization}&lt;/code&gt;, and &lt;code&gt;{ads}&lt;/code&gt; with &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; based on the user's choices. The result is a cookie that the CMP treats as a valid consent record.&lt;/p&gt;

&lt;p&gt;ProtoConsent currently supports signatures for dozens of CMPs, including both widely-deployed frameworks and platform-specific consent implementations. The full list is maintained in the source repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers of response
&lt;/h2&gt;

&lt;p&gt;Cookie injection alone doesn't cover every case. Some CMPs use server-side consent mechanisms, some use localStorage, and some load asynchronously. ProtoConsent uses three complementary layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layer 1: Cookie injection&lt;/strong&gt; - writes the consent cookie with the user's purpose choices. This is the primary mechanism and works for the majority of CMPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layer 2: Cosmetic CSS&lt;/strong&gt; - injects CSS rules that hide known banner selectors (&lt;code&gt;display: none !important&lt;/code&gt;). This is a safety net for CMPs where cookie injection is late or incomplete, and the only option for CMPs with server-side consent mechanisms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layer 3: Scroll unlock&lt;/strong&gt; - removes scroll-lock classes and inline styles that CMPs apply to prevent scrolling until consent is given. A MutationObserver watches for re-locking attempts for 10 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAB TCF v2.2 TC String generation
&lt;/h2&gt;

&lt;p&gt;For CMPs that read the &lt;code&gt;euconsent-v2&lt;/code&gt; cookie (the IAB Transparency and Consent Framework standard), ProtoConsent generates a valid TC String. The user's six purpose preferences are mapped to TCF's 24 purpose IDs, and the result is encoded as a base64url bitfield following the TCF v2.2 specification.&lt;/p&gt;

&lt;p&gt;This means sites that check the IAB standard for consent status see a properly formatted consent record that reflects the user's actual choices, not a blanket "accept all" or "deny all".&lt;/p&gt;

&lt;h2&gt;
  
  
  Purpose-level control, not binary
&lt;/h2&gt;

&lt;p&gt;Unlike most consent tools that treat banners as "accept" or "reject", ProtoConsent maps its six purposes to each CMP's specific categories. If you allow analytics but deny ads, the generated cookie reflects exactly that. The CMP sees a nuanced consent record, and the site can load analytics scripts while keeping ad trackers disabled.&lt;/p&gt;

&lt;p&gt;This is possible because the signature system knows how each CMP maps purposes to cookie values. The extension doesn't need to understand the CMP's UI; it just needs to speak its data format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie cleanup and privacy
&lt;/h2&gt;

&lt;p&gt;Injected cookies are deleted after 5 seconds. CMPs read their cookies synchronously during initialization (the first 1-2 seconds of page load). Once the CMP has read the cookie and decided not to show the banner, the cookie is no longer needed. Deleting it reduces HTTP overhead on subsequent requests and minimizes the consent cookie's lifetime.&lt;/p&gt;

&lt;p&gt;Each page visit generates a fresh random UUID (via &lt;code&gt;crypto.randomUUID()&lt;/code&gt;) for cookies that require one, so consent cookies cannot be used to correlate visits across navigations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with other approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ProtoConsent&lt;/th&gt;
&lt;th&gt;Click-based extensions&lt;/th&gt;
&lt;th&gt;Content blockers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cookie injection at &lt;code&gt;document_start&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;DOM interaction (simulate clicks)&lt;/td&gt;
&lt;td&gt;Block CMP script entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Before CMP loads (preventive)&lt;/td&gt;
&lt;td&gt;After banner renders (reactive)&lt;/td&gt;
&lt;td&gt;Before CMP loads (preventive)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Banner appears&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Briefly&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CMP reads preferences&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (from injected cookie)&lt;/td&gt;
&lt;td&gt;Yes (via its own UI)&lt;/td&gt;
&lt;td&gt;No (CMP never loads)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose-level control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (per-purpose values)&lt;/td&gt;
&lt;td&gt;Varies (most are all-or-nothing)&lt;/td&gt;
&lt;td&gt;No (binary block/allow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Breakage risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (CMP sees valid consent)&lt;/td&gt;
&lt;td&gt;Medium (DOM changes break selectors)&lt;/td&gt;
&lt;td&gt;High (consent wall, missing API)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This approach has honest limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-signed cookies&lt;/strong&gt;: some CMPs use server-generated tokens that cannot be replicated client-side. ProtoConsent falls back to cosmetic hiding on these sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side consent&lt;/strong&gt;: some platforms handle consent entirely via server endpoints. Cosmetic fallback applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site-specific tokens&lt;/strong&gt;: some CMPs use site-specific identifiers that cannot be templated. Cosmetic fallback applies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent walls&lt;/strong&gt;: sites that tie consent to a paywall are not circumvented by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TC String vendor sections&lt;/strong&gt;: the generated TC String has empty vendor consent sections. CMPs that check for specific vendor IDs may not fully accept it, though in practice CMPs read the purpose bits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One piece of the puzzle
&lt;/h2&gt;

&lt;p&gt;CMP auto-response handles the consent interface layer. ProtoConsent also enforces purpose choices at the network level, blocking requests associated with denied purposes via &lt;code&gt;declarativeNetRequest&lt;/code&gt; before they leave the browser. A conditional &lt;a href="https://globalprivacycontrol.org/" rel="noopener noreferrer"&gt;GPC&lt;/a&gt; signal (&lt;code&gt;Sec-GPC&lt;/code&gt;) is sent per site when privacy-relevant purposes are denied, carrying legal weight under CCPA/CPRA. Together, they form a layered system: the banner is pre-empted, the requests are blocked, and the site receives the user's preference as a standards-based signal.&lt;/p&gt;

&lt;p&gt;ProtoConsent is free and open source (GPL-3.0+). See the &lt;a href="https://github.com/ProtoConsent/ProtoConsent" rel="noopener noreferrer"&gt;source code&lt;/a&gt; on GitHub.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A .well-known file for website privacy declarations</title>
      <dc:creator>ProtoConsent</dc:creator>
      <pubDate>Sun, 26 Apr 2026 01:46:22 +0000</pubDate>
      <link>https://dev.to/protoconsent/a-well-known-file-for-website-privacy-declarations-2f16</link>
      <guid>https://dev.to/protoconsent/a-well-known-file-for-website-privacy-declarations-2f16</guid>
      <description>&lt;p&gt;&lt;em&gt;How websites can declare their data practices in a machine-readable format&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrbte32utk8ghqibx53l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrbte32utk8ghqibx53l.png" alt="Site declaration in the extension" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Most websites have a privacy policy. Most people don't read them. What if a website could declare its data practices in a machine-readable format that a browser extension could read, display, and compare against the user's preferences?&lt;/p&gt;

&lt;p&gt;That's what &lt;code&gt;.well-known/protoconsent.json&lt;/code&gt; does. It follows the same pattern as &lt;code&gt;security.txt&lt;/code&gt; (&lt;a href="https://www.rfc-editor.org/rfc/rfc9116" rel="noopener noreferrer"&gt;RFC 9116&lt;/a&gt;) and &lt;code&gt;.well-known/change-password&lt;/code&gt;: a static file at a standard path that tools can discover and consume automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;A minimal declaration for a blog that uses privacy-friendly analytics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protoconsent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purposes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"functional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legitimate_interest"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Privacy-friendly Analytics"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"days"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file declares which of ProtoConsent's six purposes the site uses, under what legal basis, with which providers, and for how long data is retained. Purposes not included are treated as "not declared" (the site makes no claim). Setting &lt;code&gt;"used": false&lt;/code&gt; explicitly states a purpose is not active.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six purposes
&lt;/h2&gt;

&lt;p&gt;The declaration uses the same purpose taxonomy as the ProtoConsent extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;functional&lt;/strong&gt; - core site functionality (login, cart, preferences)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;analytics&lt;/strong&gt; - usage measurement and reporting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ads&lt;/strong&gt; - advertising and ad targeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;personalization&lt;/strong&gt; - content personalization based on user behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;third_parties&lt;/strong&gt; - embedded third-party services (maps, videos, social widgets)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;advanced_tracking&lt;/strong&gt; - cross-site tracking, fingerprinting, user profiling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each purpose, the site can declare: whether it's used, the legal basis (aligned with GDPR Article 6), providers involved, data sharing scope, and retention period.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fuller example
&lt;/h2&gt;

&lt;p&gt;An e-commerce site with ads, analytics, and third-party sharing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protoconsent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-13"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purposes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"functional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contractual"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Analytics provider"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"years"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Ad network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Retargeting pixel"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sharing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"third_parties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"months"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"personalization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"until_withdrawal"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"third_parties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legal_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sharing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"third_parties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"years"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"advanced_tracking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data_handling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"storage_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"international_transfers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://shop.example.com/privacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rights"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://shop.example.com/privacy#your-rights"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the extension uses it
&lt;/h2&gt;

&lt;p&gt;When you visit a site that serves a &lt;code&gt;.well-known/protoconsent.json&lt;/code&gt;, the ProtoConsent extension fetches and validates it. The declared practices are displayed in a side panel alongside the user's own preferences, using &lt;a href="https://consentcommons.com/" rel="noopener noreferrer"&gt;Consent Commons&lt;/a&gt; icons for each purpose.&lt;/p&gt;

&lt;p&gt;This creates a two-column view: what the site says it does (declaration) and what the user wants (preferences). Users can see at a glance whether a site's stated practices align with their choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-assertion, not certification
&lt;/h2&gt;

&lt;p&gt;The declaration is a voluntary, self-asserted transparency signal. It does not change how the extension enforces user preferences. Publishing a &lt;code&gt;protoconsent.json&lt;/code&gt; file does not prove actual technical behavior: a site could declare &lt;code&gt;"ads": { "used": false }&lt;/code&gt; while still loading ad trackers. The extension always enforces the user's own profile.&lt;/p&gt;

&lt;p&gt;Think of it like &lt;code&gt;security.txt&lt;/code&gt;: it's a machine-readable way for sites to say "here's what we do" that tools can consume. Trust comes from the declaration being public, inspectable, and comparable against observed behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complementary to existing standards
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPC (Sec-GPC)&lt;/strong&gt;: signals user preference (browser to site). The declaration signals site practices (site to browser). They are complementary directions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProtoConsent SDK&lt;/strong&gt;: enables dynamic interaction (page queries extension). The declaration enables static discovery (extension reads site). A site can use one, both, or neither.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent Commons&lt;/strong&gt;: the purpose categories and legal basis values align with the &lt;a href="https://consentcommons.com/" rel="noopener noreferrer"&gt;Consent Commons&lt;/a&gt; taxonomy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Publishing a declaration takes a few minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt;: use the &lt;a href="https://protoconsent.org/generate.html" rel="noopener noreferrer"&gt;online generator&lt;/a&gt; to create your file interactively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt;: check your file with the &lt;a href="https://protoconsent.org/validate.html" rel="noopener noreferrer"&gt;online validator&lt;/a&gt; or the &lt;a href="https://github.com/ProtoConsent/validate-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; for CI/CD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt;: place the file at &lt;code&gt;/.well-known/protoconsent.json&lt;/code&gt; on your domain. For GitHub Pages, add a &lt;code&gt;.nojekyll&lt;/code&gt; file so the &lt;code&gt;.well-known&lt;/code&gt; directory is served.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List your site&lt;/strong&gt;: add it to the &lt;a href="https://protoconsent.org/directory.html" rel="noopener noreferrer"&gt;public directory&lt;/a&gt; of sites with declarations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full specification, see &lt;a href="https://github.com/ProtoConsent/ProtoConsent/blob/main/design/spec/protoconsent-well-known.md" rel="noopener noreferrer"&gt;protoconsent-well-known.md&lt;/a&gt; on GitHub. The &lt;a href="https://github.com/ProtoConsent/ProtoConsent/blob/main/docs/schema/v0.2.json" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; (v0.2) is also available for programmatic validation.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>json</category>
    </item>
    <item>
      <title>Purpose-based consent: a missing layer in the browser</title>
      <dc:creator>ProtoConsent</dc:creator>
      <pubDate>Sun, 26 Apr 2026 01:43:34 +0000</pubDate>
      <link>https://dev.to/protoconsent/purpose-based-consent-a-missing-layer-in-the-browser-nl5</link>
      <guid>https://dev.to/protoconsent/purpose-based-consent-a-missing-layer-in-the-browser-nl5</guid>
      <description>&lt;p&gt;&lt;em&gt;Why organizing privacy choices around purposes, not vendors, changes everything&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc8f1g41uq6a4qzuaapy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc8f1g41uq6a4qzuaapy.png" alt="Purpose-based privacy control" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with consent today
&lt;/h2&gt;

&lt;p&gt;There is no browser-level place where a user can say "I allow analytics but not ads on this site" and have it enforced consistently. Privacy choices today are scattered across per-site dialogs, each with different language, different categories, and different defaults. The result is not informed consent - it is consent fatigue.&lt;/p&gt;

&lt;p&gt;Existing tools sit at two extremes. Content blockers operate on domains and filter lists: effective, but blunt. Consent management platforms (CMPs) operate per site and per vendor: flexible for the site, opaque for the user. There is no browser-level layer in between where you can express intent by purpose and have it enforced consistently across sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why purpose-based
&lt;/h2&gt;

&lt;p&gt;ProtoConsent organizes decisions around &lt;em&gt;purposes of data use&lt;/em&gt;: functional, analytics, ads, personalization, third-party services, and advanced tracking. Not around vendors, cookies, or domains.&lt;/p&gt;

&lt;p&gt;Purpose is the only abstraction that connects three things simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regulation&lt;/strong&gt;: major privacy frameworks organize consent around purpose limitation. GDPR, CCPA/CPRA, LGPD, PIPL, PIPA, and APPI all use purpose as the fundamental unit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human comprehension&lt;/strong&gt;: people think "I don't want ads tracking me", not "I don't want requests to doubleclick.net". Purpose maps to how users actually reason about their choices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viable enforcement&lt;/strong&gt;: purposes can be mapped to domain categories and filter rules that browser extension APIs can enforce at the network level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A vendor-based model would fragment decisions across hundreds of entities. A cookie-based model would ignore network-level tracking. Purpose sits at the right level of abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why browser-level
&lt;/h2&gt;

&lt;p&gt;ProtoConsent places enforcement in the browser, not in the site or in a backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No delegation to sites&lt;/strong&gt;: enforcement does not depend on each site honoring preferences. The browser blocks requests before they leave your device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No backend&lt;/strong&gt;: no central server, no accounts, no cloud sync. All state is local.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: the same choice applies the same way across sites, rather than being re-negotiated per banner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser is the only place where you can block requests, emit privacy signals like &lt;a href="https://globalprivacycontrol.org/" rel="noopener noreferrer"&gt;GPC&lt;/a&gt;, and show the user what happened, all without introducing new remote points of control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Express, enforce, observe
&lt;/h2&gt;

&lt;p&gt;ProtoConsent starts from a single premise: consent is only meaningful if the user can &lt;strong&gt;express&lt;/strong&gt; it in understandable terms, &lt;strong&gt;enforce&lt;/strong&gt; it technically, and &lt;strong&gt;observe&lt;/strong&gt; its effects. If any of the three is missing, the system fails.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt;: per-site profiles and purpose toggles let you say "on this site, allow analytics but deny ads" from a single popup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce&lt;/strong&gt;: the browser blocks requests associated with denied purposes before they leave your device. A conditional &lt;a href="https://globalprivacycontrol.org/" rel="noopener noreferrer"&gt;GPC&lt;/a&gt; signal is sent per site, with legal weight under CCPA/CPRA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observe&lt;/strong&gt;: blocked request counters, a real-time log, and per-domain purpose attribution show you exactly what enforcement does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a feedback loop: the user decides, the browser enforces, the user sees the result. Consent becomes a process, not a single click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcement is a means, not an end
&lt;/h2&gt;

&lt;p&gt;ProtoConsent uses curated blocklists to enforce user choices, but blocking is not the goal: it is the mechanism that makes consent meaningful. The current core set is built from public blocklists, organized by purpose, with cross-source validation and an explicit safelist. Path-based precision (blocking &lt;code&gt;google.com/pagead/&lt;/code&gt; instead of all of &lt;code&gt;google.com&lt;/code&gt;) prioritizes correctness over exhaustiveness.&lt;/p&gt;

&lt;p&gt;Optional extended lists provide broader coverage with curated third-party sources for users who want it, without changing the core model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voluntary site cooperation
&lt;/h2&gt;

&lt;p&gt;ProtoConsent supports two optional ways for websites to participate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Site declarations&lt;/strong&gt;: a website publishes a &lt;code&gt;.well-known/protoconsent.json&lt;/code&gt; file declaring its purposes, legal bases, and providers. The extension displays it alongside user preferences. It is a transparency signal, not enforcement evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK&lt;/strong&gt;: websites can query the user's consent state per purpose and adapt their behavior accordingly. The SDK is read-only and returns &lt;code&gt;null&lt;/code&gt; if no extension is present.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both paths are optional. ProtoConsent works without any site integration. Sites that cooperate add transparency, not a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ProtoConsent is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a full ad blocker&lt;/strong&gt;: its goal is purpose-based consent enforcement, not exhaustive tracking coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a consent management platform&lt;/strong&gt;: it does not manage consent on behalf of sites or negotiate with vendors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a VPN or anonymity tool&lt;/strong&gt;: browser-level enforcement cannot prevent server-side processing or offline correlation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a legal compliance tool&lt;/strong&gt;: it provides technical mechanisms for consent, not legal adjudication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ProtoConsent adds a layer that didn't exist: a personal consent control panel in the browser, organized around purposes, that can work alongside the tools you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;ProtoConsent is free and open source (GPL-3.0+ for the extension, MIT for the SDK). Available on the &lt;a href="https://microsoftedge.microsoft.com/addons/detail/protoconsent/djghmcahfjgmeiocpgkdgengofconfoo" rel="noopener noreferrer"&gt;Edge Add-ons Store&lt;/a&gt;. Try the &lt;a href="https://demo.protoconsent.org" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;, or read the &lt;a href="https://protoconsent.org/developers.html" rel="noopener noreferrer"&gt;developer guide&lt;/a&gt; to integrate your site.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/ProtoConsent/ProtoConsent" rel="noopener noreferrer"&gt;github.com/ProtoConsent/ProtoConsent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
