From ae61c20791ea6b5ad6e9f8cf39cf8d2580afd104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 10 Jun 2026 09:41:31 +0200 Subject: [PATCH] Match cookie-database denylist on domain forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cookie-database aggregator backstop normalised the agent's third-party name and looked it up against bare brand keys, but normalizeAlnum folds the eTLD into the key (cookiedatabase.org -> cookiedatabaseorg). Domain- and URL-form attributions therefore slipped past the exact lookup, letting noisy aggregator names be accepted instead of discarded. Add uri.DomainLabel to reduce a host-like string to its primary registrable label and check it alongside the normalised name, so both brand ("Cookiepedia") and domain forms ("cookiedatabase.org", "https://www.cookiepedia.co.uk/list") resolve to the same key. Signed-off-by: Émile Ré --- .../tracker_mapping_agent_test.go | 5 +++ pkg/cookiebanner/tracker_mapping_worker.go | 20 +++++++++--- pkg/uri/uri.go | 29 +++++++++++++++++ pkg/uri/uri_test.go | 31 +++++++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/pkg/cookiebanner/tracker_mapping_agent_test.go b/pkg/cookiebanner/tracker_mapping_agent_test.go index 385722635..9956e9051 100644 --- a/pkg/cookiebanner/tracker_mapping_agent_test.go +++ b/pkg/cookiebanner/tracker_mapping_agent_test.go @@ -106,8 +106,13 @@ func TestNameIsCookieDatabaseAggregator(t *testing.T) { {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: "cookiedatabase domain form is denied", vendor: "cookiedatabase.org", expected: true}, + {name: "cookifi domain form is denied", vendor: "cookifi.com", expected: true}, + {name: "cookiepedia url form is denied", vendor: "https://www.cookiepedia.co.uk/list", expected: true}, + {name: "cookieserve subdomain form is denied", vendor: "scan.cookieserve.com", expected: true}, {name: "onetrust is allowed", vendor: "OneTrust", expected: false}, {name: "cookiebot is allowed", vendor: "Cookiebot", expected: false}, + {name: "cookiebot domain form is allowed", vendor: "cookiebot.com", 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}, diff --git a/pkg/cookiebanner/tracker_mapping_worker.go b/pkg/cookiebanner/tracker_mapping_worker.go index 4de982754..39353c94a 100644 --- a/pkg/cookiebanner/tracker_mapping_worker.go +++ b/pkg/cookiebanner/tracker_mapping_worker.go @@ -765,15 +765,25 @@ var cookieDatabaseAggregators = map[string]struct{}{ // 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. +// attributed a tracker. The agent may return either a brand name +// ("Cookiepedia") or a domain form ("cookiedatabase.org"); the latter +// would survive a plain normalised lookup because normalizeAlnum folds +// the eTLD into the key (e.g. "cookiedatabaseorg"). To catch both forms +// the candidate is also reduced to its primary domain label before the +// alphanumeric-normalised lookup. The comparison is alphanumeric- +// normalised so spacing, punctuation, and casing differences do not +// matter. func nameIsCookieDatabaseAggregator(name string) bool { - normalized := normalizeAlnum(name) - if normalized == "" { + if _, ok := cookieDatabaseAggregators[normalizeAlnum(name)]; ok { + return true + } + + label := normalizeAlnum(uri.DomainLabel(name)) + if label == "" { return false } - _, ok := cookieDatabaseAggregators[normalized] + _, ok := cookieDatabaseAggregators[label] return ok } diff --git a/pkg/uri/uri.go b/pkg/uri/uri.go index a787c2c66..dda6da817 100644 --- a/pkg/uri/uri.go +++ b/pkg/uri/uri.go @@ -103,6 +103,35 @@ func ExtractDomain(rawURL string) string { return domain } +// DomainLabel extracts the primary registrable label from a host-like +// string. It accepts bare hostnames, full URLs, and host:port forms: +// +// "cookiedatabase.org" → "cookiedatabase" +// "https://www.cookiedatabase.org/list" → "cookiedatabase" +// "Cookiepedia" → "" (no public suffix) +// +// Returns an empty string when the input carries no registrable domain +// (e.g. a bare brand name or an unparseable value). +func DomainLabel(raw string) string { + host := strings.ToLower(strings.TrimSpace(raw)) + if i := strings.Index(host, "://"); i != -1 { + host = host[i+len("://"):] + } + + host, _, _ = strings.Cut(host, "/") + host, _, _ = strings.Cut(host, "?") + host, _, _ = strings.Cut(host, ":") + + domain, err := publicsuffix.EffectiveTLDPlusOne(host) + if err != nil { + return "" + } + + label, _, _ := strings.Cut(domain, ".") + + return label +} + // FilterFirstPartyDomains removes domains that match the eTLD+1 of // siteOrigin. Tracker scripts loaded through a first-party proxy (e.g. // t.probo.com proxying PostHog on a probo.com site) share the site's diff --git a/pkg/uri/uri_test.go b/pkg/uri/uri_test.go index ad62a8fe5..7c0984bce 100644 --- a/pkg/uri/uri_test.go +++ b/pkg/uri/uri_test.go @@ -292,6 +292,37 @@ func TestExtractDomain(t *testing.T) { } } +func TestDomainLabel(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + raw string + want string + }{ + {"bare domain", "cookiedatabase.org", "cookiedatabase"}, + {"domain with www", "www.cookiedatabase.org", "cookiedatabase"}, + {"full url", "https://www.cookiedatabase.org/list", "cookiedatabase"}, + {"url with query", "https://cookifi.com/?ref=x", "cookifi"}, + {"host with port", "cookieserve.com:8443", "cookieserve"}, + {"co.uk tld", "https://www.cookiepedia.co.uk/list", "cookiepedia"}, + {"uppercase", "CookieDatabase.ORG", "cookiedatabase"}, + {"surrounding spaces", " cookifi.com ", "cookifi"}, + {"bare brand no suffix", "Cookiepedia", ""}, + {"empty string", "", ""}, + } + + for _, tt := range tests { + t.Run( + tt.name, + func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, DomainLabel(tt.raw)) + }, + ) + } +} + func TestFilterFirstPartyDomains(t *testing.T) { t.Parallel()