Filter first-party domains from tracker mapping

Tracker scripts loaded through a first-party reverse proxy (e.g.
t.probo.com proxying PostHog) share the scanned site's eTLD+1 and
were incorrectly matched against the site owner's own
CommonThirdParty entry in matchByDomain. This caused trackers like
ph_phc_* to be attributed to the site owner instead of PostHog.

Load the CookieBanner origin in Process and pass it to both
matchByDomain and identifyWithAgent. Both now filter out initiator
domains whose eTLD+1 matches the site before querying the catalog
or feeding domains to the LLM agent. The prompt is also updated to
warn about proxy domains.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-28 22:39:13 +02:00
parent 979486020e
commit 9dfd04b449
4 changed files with 122 additions and 4 deletions

View File

@@ -29,6 +29,7 @@ import (
"go.probo.inc/probo/pkg/llm"
"go.probo.inc/probo/pkg/slug"
"go.probo.inc/probo/pkg/thirdparty"
"go.probo.inc/probo/pkg/uri"
)
type trackerMappingHandler struct {
@@ -109,20 +110,27 @@ func (h *trackerMappingHandler) Process(ctx context.Context, tp coredata.Tracker
if tp.CommonTrackerPatternID != nil {
commonPatternID = tp.CommonTrackerPatternID
} else {
scope := coredata.NewScopeFromObjectID(tp.ID)
var banner coredata.CookieBanner
if err := banner.LoadByID(ctx, tx, scope, tp.CookieBannerID); err != nil {
return fmt.Errorf("cannot load cookie banner for domain filtering: %w", err)
}
commonPatternID, err = h.matchByPattern(ctx, tx, tp)
if err != nil {
return fmt.Errorf("cannot match by pattern: %w", err)
}
if commonPatternID == nil {
commonPatternID, err = h.matchByDomain(ctx, tx, tp)
commonPatternID, err = h.matchByDomain(ctx, tx, tp, banner.Origin)
if err != nil {
return fmt.Errorf("cannot match by domain: %w", err)
}
}
if commonPatternID == nil && h.mappingAgent != nil {
commonPatternID, err = h.identifyWithAgent(ctx, tx, tp)
commonPatternID, err = h.identifyWithAgent(ctx, tx, tp, banner.Origin)
if err != nil {
return fmt.Errorf("cannot identify with agent: %w", err)
}
@@ -212,10 +220,16 @@ func (h *trackerMappingHandler) matchByPattern(
// overlap the pattern's observed initiator domains, and upserts a
// CommonTrackerPattern linking the two. As with matchByPattern,
// third-party resolution is deferred to promoteThirdParty.
//
// Domains that share the scanned site's eTLD+1 are filtered out before
// querying. Tracker scripts loaded through a first-party proxy (e.g.
// t.probo.com proxying PostHog on a probo.com site) would otherwise
// match the site owner's own CommonThirdParty entry.
func (h *trackerMappingHandler) matchByDomain(
ctx context.Context,
tx pg.Tx,
tp coredata.TrackerPattern,
siteOrigin string,
) (*gid.GID, error) {
var trackers coredata.DetectedTrackers
@@ -224,6 +238,8 @@ func (h *trackerMappingHandler) matchByDomain(
return nil, fmt.Errorf("cannot load initiator domains: %w", err)
}
domains = uri.FilterFirstPartyDomains(domains, siteOrigin)
if len(domains) == 0 {
return nil, nil
}
@@ -266,6 +282,7 @@ func (h *trackerMappingHandler) identifyWithAgent(
ctx context.Context,
tx pg.Tx,
tp coredata.TrackerPattern,
siteOrigin string,
) (*gid.GID, error) {
var trackers coredata.DetectedTrackers
@@ -274,6 +291,8 @@ func (h *trackerMappingHandler) identifyWithAgent(
h.logger.WarnCtx(ctx, "cannot load initiator domains for agent", log.Error(err))
}
domains = uri.FilterFirstPartyDomains(domains, siteOrigin)
prompt := buildAgentPrompt(tp, domains)
agentCtx, cancel := context.WithTimeout(ctx, agentTimeout)