From a68c2ef108caa6f91049f661b06e6406971b5a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 9 Jun 2026 21:38:41 +0200 Subject: [PATCH] Ignore cookie-database sites in tracker mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cookie-database and consent-directory sites (Cookifi, Cookiepedia, cookiedatabase.org, CookieServe, ...) rank highly in web search only because they catalog cookies, not because they set them. The mapping agent could attribute a tracker to the directory operator itself instead of the vendor the page names. Instruct the agent to treat such results as reference directories and extract the named vendor, never the operator, while keeping a CMP's own product cookie attributable (OptanonConsent -> OneTrust, CookieConsent -> Cookiebot). Add a conservative code backstop that discards attributions to pure aggregators, scoped to exclude CMP vendors so legitimate own-cookie attributions survive. Signed-off-by: Émile Ré --- .../prompts/tracker_identification.txt.tmpl | 2 + .../tracker_mapping_agent_test.go | 33 ++++++++++++ pkg/cookiebanner/tracker_mapping_worker.go | 50 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/pkg/cookiebanner/prompts/tracker_identification.txt.tmpl b/pkg/cookiebanner/prompts/tracker_identification.txt.tmpl index 7ee9256f7..7f31b2e82 100644 --- a/pkg/cookiebanner/prompts/tracker_identification.txt.tmpl +++ b/pkg/cookiebanner/prompts/tracker_identification.txt.tmpl @@ -24,6 +24,8 @@ Return a structured JSON response with: - Stop searching once you get a confident match; do not exhaust all query slots if the first one succeeds. - When evaluating web search results, verify that the tracker name discussed in the result shares a meaningful prefix with the pattern you are identifying. Trackers with different prefixes are distinct — for example, _hjCookieTest (Hotjar's _hj prefix) must not be confused with a pattern named cookietest (no _hj prefix). If the search result discusses a tracker whose prefix does not match, discard it and continue searching or lower your confidence. - A generic token shared with a vendor's terminology is NOT a match when it appears as a suffix or substring behind a different, meaningful prefix. The leading prefix is what attributes a vendor, not a common word elsewhere in the name. For example, probo_distinct_id carries the custom prefix probo_, so it must NOT be attributed to Mixpanel merely because Mixpanel uses a distinct_id key — the prefix probo_ does not belong to Mixpanel. Likewise, a key ending in _session or _uid is not attributable to a vendor just because that vendor also uses such a word. + - Some web search results come from cookie-database or cookie-banner directory sites (e.g. cookifi.com, cookiepedia.co.uk, cookiedatabase.org, cookie-script.com, cookieserve.com, and similar "cookie database" / "cookie scanner" directories). These rank highly only because they catalog cookies, not because they set them. Treat such a result ONLY as a reference directory: read which actual vendor the page names as the setter of the tracker, and attribute to THAT vendor. NEVER set third_party_name to the directory operator itself (e.g. "Cookifi", "Cookiepedia", "Cookie-Script", "CookieDatabase", "CookieServe") — they are never the third party that set the tracker. If such a page names no concrete vendor for the tracker, ignore it and continue searching or return an empty third_party_name with third_party_confidence below 0.3. + - Exception: a consent-management vendor's OWN product cookie is still attributable to that vendor on the strength of its naming convention, independent of where the search result was hosted — e.g. OptanonConsent / OptanonAlertBoxClosed -> OneTrust, CookieConsent -> Cookiebot, cookieyes-consent -> CookieYes. Judge that on the naming convention alone, the same way you would any other vendor. 4. Common cookie naming conventions to recognize: - _ga*, _gid, _gat*: Google Analytics diff --git a/pkg/cookiebanner/tracker_mapping_agent_test.go b/pkg/cookiebanner/tracker_mapping_agent_test.go index 4ad01f14d..385722635 100644 --- a/pkg/cookiebanner/tracker_mapping_agent_test.go +++ b/pkg/cookiebanner/tracker_mapping_agent_test.go @@ -92,6 +92,39 @@ func TestNameMatchesSiteDomain(t *testing.T) { } } +func TestNameIsCookieDatabaseAggregator(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + vendor string + expected bool + }{ + {name: "cookifi is denied", vendor: "Cookifi", expected: true}, + {name: "cookiepedia is denied", vendor: "cookiepedia", expected: true}, + {name: "cookie database is denied", vendor: "Cookie Database", expected: true}, + {name: "cookieserve is denied", vendor: "CookieServe", expected: true}, + {name: "spacing and casing insensitive", vendor: " COOK IFI ", expected: true}, + {name: "punctuation insensitive", vendor: "Cookie_Database", expected: true}, + {name: "onetrust is allowed", vendor: "OneTrust", expected: false}, + {name: "cookiebot is allowed", vendor: "Cookiebot", expected: false}, + {name: "cookieyes is allowed", vendor: "CookieYes", expected: false}, + {name: "cookie-script is allowed", vendor: "Cookie-Script", expected: false}, + {name: "unrelated vendor is allowed", vendor: "Google Analytics", expected: false}, + {name: "empty name", vendor: "", expected: false}, + } + + for _, tt := range tests { + t.Run( + tt.name, + func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.expected, nameIsCookieDatabaseAggregator(tt.vendor)) + }, + ) + } +} + func TestNormalizeAlnum(t *testing.T) { t.Parallel() diff --git a/pkg/cookiebanner/tracker_mapping_worker.go b/pkg/cookiebanner/tracker_mapping_worker.go index 13abc6386..4de982754 100644 --- a/pkg/cookiebanner/tracker_mapping_worker.go +++ b/pkg/cookiebanner/tracker_mapping_worker.go @@ -700,6 +700,25 @@ func (h *trackerMappingHandler) identifyWithAgent( return nil, nil } + // Cookie-database and cookie-banner directory sites (Cookifi, + // Cookiepedia, cookiedatabase.org, ...) rank highly in web search + // only because they catalog cookies, not because they set them. A + // web result hosted on one can lead the agent to attribute the + // tracker to the directory operator itself. Discard such an + // attribution so the pattern falls through to the unmatched fallback + // instead of being mapped to a database aggregator. The denylist is + // scoped to pure aggregators, so a CMP's own product cookie (e.g. + // OptanonConsent -> OneTrust) is still attributed normally. + if nameIsCookieDatabaseAggregator(identification.ThirdPartyName) { + h.logger.InfoCtx( + ctx, + "agent attributed cookie-database aggregator as third party, discarding", + log.String("pattern", tp.Pattern), + ) + + return nil, nil + } + return &agentIdentification{ result: identification, }, nil @@ -728,6 +747,37 @@ func nameMatchesSiteDomain(name, siteOrigin string) bool { normalizedName == normalizeAlnum(label) } +// cookieDatabaseAggregators holds alphanumeric-normalised names of pure +// cookie-database / cookie-banner directory operators that catalog +// cookies but never legitimately set one on a third-party site. They +// surface in web search only because they host pattern databases, so an +// attribution to one is always search-database noise. The set is kept +// deliberately narrow: consent-management vendors that DO set their own +// product cookies (Cookie-Script, OneTrust, Cookiebot, CookieYes) are +// excluded so the backstop never suppresses a legitimate own-cookie +// attribution — the prompt handles their directory pages instead. +var cookieDatabaseAggregators = map[string]struct{}{ + "cookifi": {}, + "cookiepedia": {}, + "cookiedatabase": {}, + "cookieserve": {}, +} + +// nameIsCookieDatabaseAggregator reports whether a candidate vendor name +// is a known cookie-database directory operator that must never be +// attributed a tracker. The comparison is alphanumeric-normalised so +// spacing, punctuation, and casing differences do not matter. +func nameIsCookieDatabaseAggregator(name string) bool { + normalized := normalizeAlnum(name) + if normalized == "" { + return false + } + + _, ok := cookieDatabaseAggregators[normalized] + + return ok +} + // normalizeAlnum lowercases s and keeps only ASCII letters and digits, // so vendor names and domains can be compared free of spacing, // punctuation, and casing differences (e.g. "Letaido" and "letaido.com"