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:
@@ -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,
|
||||
}
|
||||
|
||||
70
pkg/coredata/cookie_source.go
Normal file
70
pkg/coredata/cookie_source.go
Normal 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)
|
||||
}
|
||||
}
|
||||
18
pkg/coredata/migrations/20260429T064800Z.sql
Normal file
18
pkg/coredata/migrations/20260429T064800Z.sql
Normal 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;
|
||||
Reference in New Issue
Block a user