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"