Privacy and compliance

Does obfuscation break consent and tracking preferences?

A consent manager is a gate in front of your tags, and it is configured entirely by narrowing permissive defaults: deny until asked, require an explicit tick rather than an implied one, honour the browser's own privacy signal, forbid particular categories outright in particular regions, and apply a test only your application can express. Those are property names on an options object, and the gate reading them was installed rather than built. We protected a file that configures one, renamed the names a group at a time, and counted which pixels fired.

What the sample actually does

The file configures a consent gate the way a team that has read the regulations configures one. Nothing but strictly necessary tags fires before the visitor has answered. An explicit tick is required, so a category the visitor did not choose does not fire on a legitimate-interest argument. The Global Privacy Control signal is honoured, because under CCPA and CPRA that header is itself an opt-out. A rule forbids the data-sale category outright in this region. And the site supplies its own test, which refuses to profile a visitor below the age of digital consent no matter what they ticked.

Each guard gets a visitor only it refuses, and deliberately on a different axis, so that no guard can refuse before another is reached. The undecided visitor has not answered at all and is an adult. The analytics-only visitor answered, is an adult, sent no privacy signal, and simply did not tick marketing. The signal visitor ticked everything and sent Global Privacy Control. The prohibition visitor ticked everything including data sale and sent no signal. The fourteen-year-old ticked every box, answered explicitly, and sent no signal - so every option-level rule is satisfied and only the site's own gate can refuse.

The consent library is copied in unprotected. Its defaults are the permissive ones this family ships: an unanswered banner means go ahead, implied consent counts, the privacy signal is ignored, there are no category rules, and the gate asks only whether the banner was dismissed.

Protection alone was applied first on all five presets and all five behaved identically to the unprotected file.

A prohibition that became a permission

The rule forbidding data sale is an object with a category and a flag. Renaming the flag did not weaken the rule and did not remove it. It reversed it.

The measured line went from rules=datasale:prohibited to rules=datasale:permitted, and the data-sale category fired for a visitor in a region where the site's own policy forbids it. The gate did not fall through to a default. It matched a rule and permitted, and it would have logged a permission granted by policy.

That is the most dangerous shape a lost name can take, because the outcome is not an absence of a decision - it is a decision, made confidently, in the opposite direction, with a clean audit line behind it. Renaming the rule's category key instead produced the same firing by a different route: the rule stopped matching anything, so the prohibition simply never applied.

The same file gates a category the other way round, through an explicit permission list of literal strings compared by value. That list survived every arm in this area. A rule authored as "nothing is permitted unless named" stops matching and therefore stops permitting; a rule authored as "this is forbidden" stops matching and therefore starts permitting. Same edit, opposite direction, and the difference is entirely in how the policy was written.

The child who got profiled

The site's own gate refuses to profile a visitor under sixteen. The consent library cannot express that - it knows only whether the banner was dismissed.

Renaming the option that carries the gate substituted the builtin. The fourteen-year-old had answered the banner and ticked every box, so every option-level rule was satisfied, and the builtin's only question - was the banner dismissed? - was answered yes. child-profiled went from false to true.

This is the same downgrade shape found in ten other libraries across this series: the guard is not removed, it is replaced by a weaker one that agrees with it in the ordinary case. Here the ordinary case is an adult, which is almost every visitor, so the substitution is invisible in aggregate and shows up only in the population it exists to protect.

The rest of the arms, and the two that fail closed

Denying by default reverted to permitting by default, and analytics and marketing both fired for a visitor who had not answered the banner - the tags ran before the dialogue asking permission for them had been answered. Requiring an explicit tick reverted to accepting an implied one, and marketing fired for a visitor who had chosen analytics and nothing else. Honouring the privacy signal reverted to ignoring it, and marketing fired for a browser that had sent Global Privacy Control, which is an opt-out request in its own right.

The region string moved into the consent record under a renamed key rather than changing any decision, which is the quieter failure: the record still says the visitor is in a region, but the field the downstream vendor reads for it is now called something else.

Two arms failed closed instead. Renaming the visitor object's own fields - answered, ticked categories, privacy signal, age - collapsed every visitor to strictly-necessary tags only. Nothing fired for anyone. That is a revenue and attribution outage rather than a privacy incident, and it is the one arm in this article somebody would notice the same afternoon.

One arm measured no change: the consent lifetime, pinned deliberately to a value identical to the library default as a control.

The record you would show a regulator

The consent record is the artefact that matters when somebody asks you to demonstrate consent rather than assert it. Under GDPR that demonstration is the obligation; the tag firing correctly is not sufficient on its own.

Renaming the record's field names - the purposes agreed to, the timestamp, the method - left the encoded consent string well-formed and readable, with keys of _0x1, _0x2 and _0x3. Reading it back through the accessor a regulator's export would use returned purposes=MISSING timestamp=MISSING method=MISSING.

Nothing failed. The record was written, stored, and is fully intact. It simply no longer answers the question it exists to answer, and downstream vendors reading it for a purpose string get nothing. This is the third area in this series where a renamed field name survived serialisation into a format another party parses, and the receiving parser did what every well-behaved parser does with a key it does not recognise: ignored it.

What to do about it

The mechanism is not specific to consent and it is not a defect in the obfuscator. Member renaming rewrites property names inside the code it is given. An installed consent library is not inside that code, so a renamed option name is one it has never heard of, and it applies its documented default - which in this category of product is always the permissive one, because a consent library that blocked everything on installation would be uninstalled.

Scope RenameMembers with a MemberRegexp that excludes the consent library's options object, your policy rules, and the consent record itself. The record deserves particular attention because it is the piece that leaves the process and gets parsed by somebody else.

Author the policy as permissions rather than prohibitions. This pass measured both shapes in one file: the prohibition flag reversed into a permission when its name moved, and the explicit permission list survived every arm. If a category must not fire, express that by leaving it out of the permitted set rather than by setting a forbidden flag on it, so that a lost name stops permitting instead of starting to.

Then verify by observation rather than configuration. Load a page as a visitor who has consented to nothing and assert that no network request leaves for a marketing or analytics endpoint. That check does not care what any option is called, it does not care which guard was supposed to stop the request, and it is the only one here that catches every arm - including the two that fail closed, which it catches in the other direction by finding that nothing fires for anyone.

Frequently asked questions

Does protecting my JavaScript break a consent manager on its own?

Not in this measurement. The sample was protected on five profiles covering both output targets, the gate profile and the compressed profile, and all five behaved identically to the unprotected file. Every result here required member renaming pointed at property names.

What was the worst result?

Renaming the flag on a prohibition rule. The rule did not weaken and did not disappear - it reversed. The forbidden category fired, and the gate reported it as permitted because a rule matched and permitted it, which is a confident decision in the wrong direction with a clean log line behind it.

Is a permission list really safer than a prohibition flag?

In this measurement, decisively. The same file gated one category by prohibition and another by an explicit permission list of literal strings. Every arm that reversed the prohibition left the permission list intact, because a list that stops matching stops permitting, while a prohibition that stops matching starts permitting.

How did a fourteen-year-old end up profiled?

The site's own gate refuses to profile visitors under sixteen; the library's builtin only asks whether the banner was dismissed. Renaming the option carrying the gate substituted the builtin, which answered yes. The two agree for adults, so the substitution is invisible in aggregate.

Would this show up as a GDPR or CCPA problem specifically?

Several arms map directly. Tags firing before the banner is answered, marketing firing for a visitor who chose analytics only, and the Global Privacy Control signal being ignored are each an opt-out being disregarded. Separately, the consent record's field names moved, so the proof of consent you would produce on request read as missing.

Did anything fail loudly?

One group. Renaming the visitor object's own fields collapsed every visitor to strictly-necessary tags, so nothing fired for anybody. That is an attribution outage rather than a privacy incident, and it is the only arm here somebody would notice the same day.

What is the smallest change that prevents all of this?

Scope RenameMembers to exclude the consent library's options, your policy rules and the consent record, author the policy as permissions rather than prohibitions, and add one observational check: load the page having consented to nothing and assert no marketing or analytics request leaves the browser.

Related reading