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

@@ -30,15 +30,16 @@ import (
type (
Cookie struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
CookieBannerID gid.GID `db:"cookie_banner_id"`
CookieCategoryID gid.GID `db:"cookie_category_id"`
Name string `db:"name"`
Duration string `db:"duration"`
Description string `db:"description"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
CookieBannerID gid.GID `db:"cookie_banner_id"`
CookieCategoryID gid.GID `db:"cookie_category_id"`
Name string `db:"name"`
Duration string `db:"duration"`
Description string `db:"description"`
Source CookieSource `db:"source"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Cookies []*Cookie
@@ -83,6 +84,7 @@ SELECT
name,
duration,
description,
source,
created_at,
updated_at
FROM
@@ -132,6 +134,7 @@ SELECT
name,
duration,
description,
source,
created_at,
updated_at
FROM
@@ -209,6 +212,7 @@ SELECT
name,
duration,
description,
source,
created_at,
updated_at
FROM
@@ -255,6 +259,7 @@ INSERT INTO cookies (
name,
duration,
description,
source,
created_at,
updated_at
) VALUES (
@@ -266,6 +271,7 @@ INSERT INTO cookies (
@name,
@duration,
@description,
@source,
@created_at,
@updated_at
)
@@ -280,6 +286,7 @@ INSERT INTO cookies (
"name": c.Name,
"duration": c.Duration,
"description": c.Description,
"source": c.Source,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
@@ -312,6 +319,7 @@ INSERT INTO cookies (
name,
duration,
description,
source,
created_at,
updated_at
) VALUES (
@@ -323,10 +331,13 @@ INSERT INTO cookies (
@name,
@duration,
@description,
@source,
@created_at,
@updated_at
)
ON CONFLICT (cookie_banner_id, name) DO NOTHING
ON CONFLICT (cookie_banner_id, name) DO UPDATE
SET source = EXCLUDED.source, updated_at = EXCLUDED.updated_at
WHERE cookies.source != @source_script AND EXCLUDED.source = @source_script
`
args := pgx.StrictNamedArgs{
@@ -338,6 +349,8 @@ ON CONFLICT (cookie_banner_id, name) DO NOTHING
"name": c.Name,
"duration": c.Duration,
"description": c.Description,
"source": c.Source,
"source_script": CookieSourceScript,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package coredata
import (
"database/sql/driver"
"fmt"
)
type CookieSource string
const (
CookieSourceScript CookieSource = "SCRIPT"
CookieSourcePreExisting CookieSource = "PRE_EXISTING"
)
func CookieSources() []CookieSource {
return []CookieSource{
CookieSourceScript,
CookieSourcePreExisting,
}
}
func (s CookieSource) String() string {
return string(s)
}
func (s *CookieSource) Scan(value any) error {
var v string
switch val := value.(type) {
case string:
v = val
case []byte:
v = string(val)
default:
return fmt.Errorf("unsupported type for CookieSource: %T", value)
}
switch CookieSource(v) {
case CookieSourceScript:
*s = CookieSourceScript
case CookieSourcePreExisting:
*s = CookieSourcePreExisting
default:
return fmt.Errorf("invalid CookieSource value: %q", v)
}
return nil
}
func (s CookieSource) Value() (driver.Value, error) {
switch s {
case CookieSourceScript,
CookieSourcePreExisting:
return string(s), nil
default:
return nil, fmt.Errorf("invalid CookieSource: %s", s)
}
}

View File

@@ -0,0 +1,18 @@
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE.
CREATE TYPE cookie_source AS ENUM ('SCRIPT', 'PRE_EXISTING');
ALTER TABLE cookies ADD COLUMN source cookie_source NOT NULL DEFAULT 'PRE_EXISTING';
ALTER TABLE cookies ALTER COLUMN source DROP DEFAULT;