From 4435a93eca8a1a0c595d04075c4420aba6cce950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Thu, 18 Jun 2026 22:00:21 +0200 Subject: [PATCH] Rank HTTP cookie source above pre-existing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tracker-pattern source ranking collapsed HTTP into the PRE_EXISTING tier, so a cookie first enumerated as pre-existing and later re-observed only via a Set-Cookie response header stayed pre-existing. That left it on the agent-skipped tier (isPreExistingSource), even though an HTTP server-set cookie is real page evidence, not the extension-state catch-all the skip was built to suppress. Give HTTP its own rank between SCRIPT and EXTENSION (SCRIPT > HTTP > EXTENSION > PRE_EXISTING) in both sourceRank and the bestSource merge rollup, so an HTTP re-detection now promotes the pattern and re-arms mapping, unblocking the identification agent. Signed-off-by: Émile Ré --- pkg/cookiebanner/pattern_analysis_worker.go | 53 ++++++++++++------- .../pattern_analysis_worker_test.go | 34 +++++++++++- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/pkg/cookiebanner/pattern_analysis_worker.go b/pkg/cookiebanner/pattern_analysis_worker.go index d3ce012fb..1fb8821f9 100644 --- a/pkg/cookiebanner/pattern_analysis_worker.go +++ b/pkg/cookiebanner/pattern_analysis_worker.go @@ -704,11 +704,15 @@ func globMatch(pattern, name string) bool { } // sourceRank converts a CookieSource into a comparable rank that -// reflects signal strength: SCRIPT > EXTENSION > PRE_EXISTING. HTTP -// and nil collapse into the PRE_EXISTING rank because bestSource -// already normalises them; if a future caller hands us either, the -// ranking still produces a sane "no promotion" outcome against -// PRE_EXISTING/EXTENSION/SCRIPT existing values. +// reflects signal strength: SCRIPT > HTTP > EXTENSION > PRE_EXISTING. +// SCRIPT is a real page tracker observed being written by page JS; +// HTTP is a real server-set cookie (a Set-Cookie response header), +// which is page evidence just like SCRIPT and so must outrank both +// browser-extension state and the PRE_EXISTING catch-all — otherwise a +// cookie first enumerated as PRE_EXISTING and later re-observed only +// via Set-Cookie would stay PRE_EXISTING and remain agent-skipped. +// EXTENSION is high-confidence extension state; PRE_EXISTING (and nil) +// is the low-signal catch-all. func sourceRank(s *coredata.CookieSource) int { if s == nil { return 0 @@ -716,6 +720,8 @@ func sourceRank(s *coredata.CookieSource) int { switch *s { case coredata.CookieSourceScript: + return 3 + case coredata.CookieSourceHTTP: return 2 case coredata.CookieSourceExtension: return 1 @@ -733,17 +739,19 @@ func shouldPromoteSource(existing, candidate *coredata.CookieSource) bool { } // bestSource rolls up the source values of a group of exact patterns -// being merged into a single glob. Precedence is SCRIPT > EXTENSION -// > PRE_EXISTING, mirroring both the page-script-wins rule in -// detected_trackers and the asymmetric signal strength of each -// bucket: SCRIPT is high-confidence page evidence (a real page -// tracker), EXTENSION is high-confidence extension evidence, and -// PRE_EXISTING is the catch-all that may include extension state -// injected before SDK load. HTTP and nil collapse into PRE_EXISTING -// here, preserving the original two-value rollup behaviour for -// non-script values. +// being merged into a single glob. Precedence is SCRIPT > HTTP > +// EXTENSION > PRE_EXISTING, mirroring the asymmetric signal strength of +// each bucket: SCRIPT is high-confidence page evidence (a real page +// tracker), HTTP is a real server-set cookie (a Set-Cookie response +// header) and is page evidence too, EXTENSION is high-confidence +// extension state, and PRE_EXISTING is the catch-all that may include +// extension state injected before SDK load. nil collapses into +// PRE_EXISTING. func bestSource(patterns []*coredata.TrackerPattern) *coredata.CookieSource { - var hasExtension bool + var ( + hasHTTP bool + hasExtension bool + ) for _, p := range patterns { if p.Source == nil { @@ -753,19 +761,24 @@ func bestSource(patterns []*coredata.TrackerPattern) *coredata.CookieSource { switch *p.Source { case coredata.CookieSourceScript: return p.Source + case coredata.CookieSourceHTTP: + hasHTTP = true case coredata.CookieSourceExtension: hasExtension = true } } - if hasExtension { + switch { + case hasHTTP: + src := coredata.CookieSourceHTTP + return &src + case hasExtension: src := coredata.CookieSourceExtension return &src + default: + src := coredata.CookieSourcePreExisting + return &src } - - src := coredata.CookieSourcePreExisting - - return &src } // inheritedMapping rolls up the resolved org ThirdParty of a group of diff --git a/pkg/cookiebanner/pattern_analysis_worker_test.go b/pkg/cookiebanner/pattern_analysis_worker_test.go index 4fc04748c..965cfefb3 100644 --- a/pkg/cookiebanner/pattern_analysis_worker_test.go +++ b/pkg/cookiebanner/pattern_analysis_worker_test.go @@ -1159,15 +1159,45 @@ func TestShouldPromoteSource(t *testing.T) { want: false, }, { - name: "HTTP collapses to PRE_EXISTING rank: does not promote SCRIPT", + name: "HTTP does not promote to SCRIPT (HTTP ranks below SCRIPT)", existing: &script, candidate: &http, want: false, }, { - name: "HTTP collapses to PRE_EXISTING rank: equal to nil existing", + name: "nil existing promotes to HTTP", existing: nil, candidate: &http, + want: true, + }, + { + name: "PRE_EXISTING promotes to HTTP", + existing: &preExisting, + candidate: &http, + want: true, + }, + { + name: "EXTENSION promotes to HTTP (real server cookie outranks extension state)", + existing: &extension, + candidate: &http, + want: true, + }, + { + name: "HTTP promotes to SCRIPT", + existing: &http, + candidate: &script, + want: true, + }, + { + name: "HTTP does not promote to EXTENSION", + existing: &http, + candidate: &extension, + want: false, + }, + { + name: "HTTP does not promote to PRE_EXISTING", + existing: &http, + candidate: &preExisting, want: false, }, }