Skip shared infrastructure in domain matching

The deterministic tracker-mapping heuristics group patterns by shared
initiator domain, but tag managers, customer-data platforms, and
generic CDNs (Google Tag Manager, Segment, cloudfront.net, ...)
initiate trackers for many unrelated vendors. Grouping on such a
domain mis-attributes one vendor's tracker to another.

Add uri.FilterSharedInfrastructureDomains backed by a curated eTLD+1
denylist and apply it once in resolveDeterministic, so sibling
grouping, catalog domain matching, and the sibling re-enqueue cascade
all ignore shared-infrastructure hosts. Vendor-specific domains such
as google-analytics.com are intentionally kept as a same-vendor
signal. The agent path is unchanged: it still sees observed domains,
now with a prompt caveat about shared infrastructure.

Update the two sibling tests that used googletagmanager.com as the
initiator domain to a vendor domain, since that host is now stripped
before grouping.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-29 20:22:46 +02:00
parent 54c05ebe6a
commit 323fe4b5c3
4 changed files with 192 additions and 16 deletions

View File

@@ -125,3 +125,117 @@ func FilterFirstPartyDomains(domains []string, siteOrigin string) []string {
return filtered
}
// sharedInfrastructureDomains are eTLD+1 hosts that deliver trackers on
// behalf of many unrelated vendors: tag managers, customer-data
// platforms, and generic CDNs / static-asset / app-hosting domains. A
// tracker whose initiator domain is one of these tells us nothing about
// which vendor set it (e.g. a Meta pixel and a LinkedIn tag both loaded
// through Google Tag Manager would otherwise look like the same third
// party), so domain-overlap heuristics must ignore them.
//
// This set is the tuning surface for that exclusion. Two rules keep it
// safe to extend:
//
// - Only add domains that serve content for many *unrelated* vendors.
// Omit vendor-specific domains such as google-analytics.com: those
// are a legitimate same-vendor signal even though Google also runs
// Tag Manager and gstatic.
// - Entries are eTLD+1, so they must not collapse onto a vendor. We
// skip cloudflare.com for this reason (cdnjs.cloudflare.com shares
// its eTLD+1 with Cloudflare-the-vendor's own properties).
//
// For exhaustive, maintained coverage this would ideally be sourced from
// a community dataset (DuckDuckGo Tracker Radar's `cnames`/CDN entries or
// Disconnect's services list) vendored into the repo, rather than hand
// curated. This list covers the common offenders without that dependency;
// excluding a domain only ever makes grouping more conservative, so
// over-inclusion is the safe failure mode.
var sharedInfrastructureDomains = map[string]struct{}{
// Tag managers, customer-data platforms, and tag delivery.
"googletagmanager.com": {},
"segment.io": {},
"segment.com": {},
"tealium.com": {},
"tiqcdn.com": {},
"ensighten.com": {},
"adobedtm.com": {},
"mparticle.com": {},
"rudderlabs.com": {},
"rudderstack.com": {},
"tagcommander.com": {},
"commander1.com": {},
// Commercial CDNs and edge networks.
"cloudfront.net": {},
"akamai.net": {},
"akamaihd.net": {},
"akamaized.net": {},
"akamaiedge.net": {},
"edgekey.net": {},
"edgesuite.net": {},
"fastly.net": {},
"fastlylb.net": {},
"azureedge.net": {},
"azurefd.net": {},
"edgecastcdn.net": {},
"llnwd.net": {},
"hwcdn.net": {},
"cachefly.net": {},
"stackpathdns.com": {},
"stackpathcdn.com": {},
"netdna-cdn.com": {},
"netdna-ssl.com": {},
"kxcdn.com": {},
"b-cdn.net": {},
// Library, package, and static-asset CDNs.
"jsdelivr.net": {},
"unpkg.com": {},
"bootstrapcdn.com": {},
"maxcdn.com": {},
"cdnjs.com": {},
"jquery.com": {},
"aspnetcdn.com": {},
"skypack.dev": {},
"esm.sh": {},
"googleapis.com": {},
"gstatic.com": {},
// Font and media CDNs.
"typekit.net": {},
"fontawesome.com": {},
"cloudinary.com": {},
"imgix.net": {},
// Object storage and generic app / static hosting.
"amazonaws.com": {},
"github.io": {},
"githubusercontent.com": {},
"herokuapp.com": {},
"vercel.app": {},
"netlify.app": {},
"pages.dev": {},
"web.app": {},
"firebaseapp.com": {},
"wp.com": {},
}
// FilterSharedInfrastructureDomains removes eTLD+1 domains that belong to
// shared tracker-delivery infrastructure (tag managers, customer-data
// platforms, and generic CDNs). Such domains initiate trackers for many
// unrelated vendors, so a shared initiator domain among them is not a
// same-vendor signal and must not drive domain-overlap grouping. The
// input domains are expected to be eTLD+1 strings (as produced by
// ExtractDomain).
func FilterSharedInfrastructureDomains(domains []string) []string {
filtered := make([]string, 0, len(domains))
for _, d := range domains {
if _, shared := sharedInfrastructureDomains[strings.ToLower(d)]; !shared {
filtered = append(filtered, d)
}
}
return filtered
}

View File

@@ -364,3 +364,56 @@ func TestFilterFirstPartyDomains(t *testing.T) {
})
}
}
func TestFilterSharedInfrastructureDomains(t *testing.T) {
t.Parallel()
tests := []struct {
name string
domains []string
want []string
}{
{
name: "removes tag manager domain",
domains: []string{"googletagmanager.com", "posthog.com"},
want: []string{"posthog.com"},
},
{
name: "removes generic cdn domain",
domains: []string{"cloudfront.net", "hotjar.com"},
want: []string{"hotjar.com"},
},
{
name: "keeps vendor-specific domains",
domains: []string{"google-analytics.com", "stripe.com"},
want: []string{"google-analytics.com", "stripe.com"},
},
{
name: "case insensitive match",
domains: []string{"GoogleTagManager.com", "Segment.IO"},
want: []string{},
},
{
name: "mixed infra and vendor",
domains: []string{"gstatic.com", "doubleclick.net", "jsdelivr.net"},
want: []string{"doubleclick.net"},
},
{
name: "empty domains list",
domains: []string{},
want: []string{},
},
{
name: "nil domains list",
domains: nil,
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tt.want, FilterSharedInfrastructureDomains(tt.domains))
})
}
}