From a5ee0209f8ca8bc0dce7f58c78ad3cc57348e959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 22 May 2026 15:35:03 +0200 Subject: [PATCH] Filter separator-only glob templates in tracker pattern analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unrelated third-party trackers that happened to share a leading separator run (e.g. __support__, __darkreader__wasEnabledForHost, __EXT_APP_REFRESH_BLACK_SUB_DOMAINS__) were being merged under overly broad globs such as __* because templateCandidates emitted every prefix at each '_' or '-' position without requiring any fixed anchor. Add a templateHasFixedAnchor helper and apply it to both templateCandidates loops and the heuristicTemplate result so candidates consisting solely of '_', '-', and '*' are rejected. Signed-off-by: Émile Ré --- pkg/cookiebanner/pattern_analysis_worker.go | 32 ++++++++++- .../pattern_analysis_worker_test.go | 56 ++++++++++++++++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/pkg/cookiebanner/pattern_analysis_worker.go b/pkg/cookiebanner/pattern_analysis_worker.go index 996db5374..8c980a8ed 100644 --- a/pkg/cookiebanner/pattern_analysis_worker.go +++ b/pkg/cookiebanner/pattern_analysis_worker.go @@ -432,7 +432,12 @@ func heuristicTemplate(name string) (string, bool) { return "", false } - return prefix.String() + joinTokens(resultTokens, resultSeps) + suffix, true + tmpl := prefix.String() + joinTokens(resultTokens, resultSeps) + suffix + if !templateHasFixedAnchor(tmpl) { + return "", false + } + + return tmpl, true } func templateCandidates(name string) []string { @@ -440,7 +445,10 @@ func templateCandidates(name string) []string { for i, ch := range name { if ch == '_' || ch == '-' { - candidates = append(candidates, name[:i+1]+"*") + tmpl := name[:i+1] + "*" + if templateHasFixedAnchor(tmpl) { + candidates = append(candidates, tmpl) + } } } @@ -450,7 +458,9 @@ func templateCandidates(name string) []string { left := joinTokens(tokens[:pos], seps[:pos-1]) right := joinTokens(tokens[pos+1:], seps[pos+1:]) tmpl := left + string(seps[pos-1]) + "*" + string(seps[pos]) + right - candidates = append(candidates, tmpl) + if templateHasFixedAnchor(tmpl) { + candidates = append(candidates, tmpl) + } } } @@ -575,6 +585,22 @@ func joinTokens(tokens []string, seps []byte) string { return b.String() } +// templateHasFixedAnchor reports whether tmpl contains at least one +// character beyond separators and wildcards. Templates like "_*", +// "__*", "-*", "--*", "__*__" would merge unrelated third parties +// (e.g. __support__, __darkreader__wasEnabledForHost, +// __EXT_APP_REFRESH_BLACK_SUB_DOMAINS__) under a single glob, so +// candidates without any fixed alphanumeric anchor are rejected. +func templateHasFixedAnchor(tmpl string) bool { + for _, ch := range tmpl { + if ch != '*' && ch != '_' && ch != '-' { + return true + } + } + + return false +} + func globMatch(pattern, name string) bool { parts := strings.Split(pattern, "*") if len(parts) == 1 { diff --git a/pkg/cookiebanner/pattern_analysis_worker_test.go b/pkg/cookiebanner/pattern_analysis_worker_test.go index 601b38f46..41ef2fae3 100644 --- a/pkg/cookiebanner/pattern_analysis_worker_test.go +++ b/pkg/cookiebanner/pattern_analysis_worker_test.go @@ -238,6 +238,11 @@ func TestHeuristicTemplate(t *testing.T) { input: "__Secure-1PSID", changed: false, }, + { + name: "all variable tokens with leading underscores rejected", + input: "__a1b2c3d4_e5f6g7h8", + changed: false, + }, } for _, tt := range tests { @@ -293,10 +298,9 @@ func TestTemplateCandidates(t *testing.T) { }, }, { - name: "leading underscore", + name: "leading underscore drops anchor-free prefix", input: "_ga_GB2J3DLBHE", expected: []string{ - "_*", "_ga_*", "_*_GB2J3DLBHE", }, @@ -324,6 +328,38 @@ func TestTemplateCandidates(t *testing.T) { "auth0_*_abc123", }, }, + { + name: "double leading underscore drops anchor-free prefixes", + input: "__support__", + expected: []string{ + "__support_*", + "__support__*", + "_*_support__", + "__support_*_", + }, + }, + { + name: "double underscore extension key drops anchor-free prefixes", + input: "__darkreader__wasEnabledForHost", + expected: []string{ + "__darkreader_*", + "__darkreader__*", + "_*_darkreader__wasEnabledForHost", + "__*__wasEnabledForHost", + "__darkreader_*_wasEnabledForHost", + }, + }, + { + name: "double leading dash drops anchor-free prefixes", + input: "--leading-dash-foo", + expected: []string{ + "--leading-*", + "--leading-dash-*", + "-*-leading-dash-foo", + "--*-dash-foo", + "--leading-*-foo", + }, + }, } for _, tt := range tests { @@ -915,6 +951,22 @@ func TestFindMergeGroups(t *testing.T) { assert.Empty(t, groups) }, ) + + t.Run( + "unrelated double-underscore keys do not merge under anchor-free glob", + func(t *testing.T) { + t.Parallel() + + patterns := coredata.TrackerPatterns{ + makePattern("__support__", nil), + makePattern("__darkreader__wasEnabledForHost", nil), + makePattern("__EXT_APP_REFRESH_BLACK_SUB_DOMAINS__", nil), + } + + groups := findMergeGroups(patterns, 3) + assert.Empty(t, groups) + }, + ) } func TestDurationBucket(t *testing.T) {