Match cookie-database denylist on domain forms
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é <emile@probo.com>
This commit is contained in:
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user