Filter browser-extension cookies from detection

Cookies set by browser extensions are not the website operator's
compliance responsibility. This adds stack-trace inspection to
filter out extension-originated document.cookie writes, and
annotates pre-existing cookies with a source field so operators
can triage them separately.

Introduces a CookieSource enum (SCRIPT / PRE_EXISTING) across
the full stack: PostgreSQL, coredata, service, HTTP handler, and
GraphQL schema. On conflict, source is upgraded from PRE_EXISTING
to SCRIPT when a page script is later observed setting the cookie.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-29 11:09:05 +04:00
parent e5b489ce32
commit 48606f34c1
8 changed files with 149 additions and 12 deletions

View File

@@ -18,10 +18,17 @@ import { fetchJSON } from "./http";
interface DetectedCookieEntry {
name: string;
duration: string;
source: "script" | "pre-existing";
}
const DEBOUNCE_MS = 2_000;
const MAX_COOKIES_PER_REQUEST = 100;
const EXTENSION_URL_RE = /(?:chrome|moz|safari-web)-extension:\/\//;
function isExtensionCaller(): boolean {
const stack = new Error().stack ?? "";
return EXTENSION_URL_RE.test(stack);
}
export class CookieDetector {
private readonly reportUrl: URL;
@@ -81,6 +88,7 @@ export class CookieDetector {
private onCookieSet(raw: string): void {
if (isDeletion(raw)) return;
if (isExtensionCaller()) return;
const name = parseCookieName(raw);
if (!name || this.knownNames.has(name) || this.reported.has(name)) return;
@@ -88,7 +96,7 @@ export class CookieDetector {
const duration = parseDuration(raw);
this.reported.add(name);
this.pending.set(name, { name, duration });
this.pending.set(name, { name, duration, source: "script" });
this.scheduleFlush();
}
@@ -102,7 +110,7 @@ export class CookieDetector {
continue;
}
this.reported.add(name);
this.pending.set(name, { name, duration: "session" });
this.pending.set(name, { name, duration: "session", source: "pre-existing" });
}
if (this.pending.size > 0) {