Commit Graph

35 Commits

Author SHA1 Message Date
Émile Ré
9156d6a16a Add wsl linter and fix
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 09:27:28 +04:00
Émile Ré
0606416b39 Add SDK version to cookie banner logs via middleware
Extract X-SDK-Version from request headers in a dedicated
middleware and store it in context. All ErrorCtx calls in the
cookie banner handler now include the sdk_version field.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-13 12:49:13 +04:00
Émile Ré
e739473bcd Derive consent mode from geolocation, not banner config
The consent mode is now determined dynamically by the visitor's
country and its applicable regulation. The configured consent_mode
column is dropped from cookie_banners and added to
cookie_consent_records to persist the geo-derived mode at
consent-recording time. When no regulation matches, the default
is OPT_OUT.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-13 12:49:13 +04:00
Émile Ré
ecb9bd747e Fix empty country code stored in consent records
When IP geolocation returned no matching CIDR block, LookupCountryByIP
returned an empty string with nil error. The handler took the address of
that empty string, producing a non-nil pointer to "", which was inserted
into the database. Guard against this by returning nil when the resolved
country code is empty, and backfill existing rows with a migration.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 20:04:54 +04:00
Émile Ré
c8f130ed45 cookiebanner: address PR review feedback
- Detectors: keep batched entries in `pending` until the POST succeeds
  and guard against concurrent flushes, so transient network errors no
  longer silently drop detection reports.
- Worker: add stable tie-breakers to the merge-candidate sort so the
  greedy assignment produces deterministic groups across runs.
- Handler: skip resource entries with an empty URL (zero-value `uri.URI`
  when the `url` field is missing) before persisting them.
- Third-party detector: allow same-origin service worker scripts through
  `processResource` -- service workers are always same-origin by spec,
  so the previous filter made `wrapServiceWorker` unreachable.
- Resource row edit: bump the description cell `colSpan` to 3 so the
  edit row spans all five table columns.
- Resolver: handle `ErrSameResourceCategoryMove` explicitly so the no-op
  move returns a validation error instead of an internal one.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
caac9c76db cookiebanner: detect service workers and Cache Storage buckets
A registered service worker is a URL-shaped artifact (origin+path of
the worker script), so it goes in tracker_resources as a new
SERVICE_WORKER resource type. A Cache Storage bucket is an opaque
named string with no URL, so it goes in detected_trackers as a new
CACHE_STORAGE tracker type.

Frontend:
  - StorageDetector wraps caches.open() and enumerates caches.keys()
    on start to surface pre-existing buckets that pre-date the SDK
    load (service workers commonly populate caches eagerly on
    install).
  - ThirdPartyDetector wraps navigator.serviceWorker.register() and
    enumerates getRegistrations() on start.

Both wrappers degrade silently on insecure contexts where these APIs
are unavailable.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
2b3449de1a cookiebanner: broaden resource detection via PerformanceObserver
ThirdPartyDetector previously only saw <script src> and <iframe src>
because it scanned the DOM and watched mutations. Add a single
PerformanceObserver({type:'resource', buffered:true}) that picks up
everything the browser actually loaded:

  - tracking pixels (<img>, <picture>, srcset)
  - cross-origin stylesheets and web fonts
  - fetch / XHR / sendBeacon / ping calls (SDK call-homes)
  - video, audio, embed, object media

initiatorType is mapped to six new tracker_resource_type enum values
(IMAGE, STYLESHEET, FONT, BEACON, FETCH, MEDIA) and the existing
upsert path in tracker_resources picks them up unchanged.

Closes a real gap with headless cookie scanners: most SDKs phone home
via beacons after their script is gone, and the DOM scan never saw it.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
d17c8ba044 cookiebanner: capture script initiator URL on detected trackers
When third-party JS sets a cookie or writes to local/sessionStorage
inside a customer page, the SDK now walks the synchronous call stack
to find the first non-extension, non-Probo, non-first-party http(s)
URL. That origin+path is sent as initiator_url on the report payload,
persisted in a new nullable column on detected_trackers, and preserved
across upserts via COALESCE.

This unlocks per-vendor attribution for cookies and storage writes
without needing pattern name matching, so future categorisation logic
can simply look up the initiator URL in the existing tracker_resources
table and inherit that vendor's category.

GraphQL/MCP exposure is intentionally deferred -- the column is captured
now, surfaced later.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
93407f6431 coredata: split SCRIPT/IFRAME into tracker_resources table
Move resource tracking (scripts, iframes) out of the pattern-based
tracker_patterns/detected_trackers machinery into its own
tracker_resources table keyed by (banner, type, origin, path).

- Add migration that creates the tracker_resource_type enum, the
  tracker_resources table with a unique index, drops existing
  SCRIPT/IFRAME rows (not yet in production), and recreates the
  tracker_type enum without those values.
- Add TrackerResource coredata model with full CRUD, Upsert (bumps
  last_detected_at on conflict), list/count/move operations, filter,
  and order field support.
- Register TrackerResourceEntityType (91) in the entity type registry.
- Drop TrackerTypeScript/TrackerTypeIframe from TrackerType enum.
- Update handler to use TrackerResourceType for resource detection.
- Temporarily stub out resource ingestion in ReportDetectedTrackers
  pending the service-layer wiring in the next commit.
- Drop SCRIPT/IFRAME from the GraphQL TrackerType enum.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +04:00
Émile Ré
c0d0221be1 Detect HTTP-header cookies via CookieStore change event
Progressive enhancement for Chromium browsers: listen on the
CookieStore change event to catch cookies set by Set-Cookie HTTP
response headers, which the document.cookie setter hook cannot see.
Adds a new "http" cookie source through the full stack.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:51 +04:00
Émile Ré
a4cb61366f Report full URL instead of bare origin for detected scripts and iframes
Strip query params and send origin+pathname so the backend can
distinguish resources served from the same domain but different paths
(e.g. gtm.js vs recaptcha/api.js on googletagmanager.com).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:48 +04:00
Émile Ré
d1f34add6f Fix cookiebanner rest API for sdk version <=0.2.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 17:57:00 +04:00
Émile Ré
5f4fd3c427 Surface regulation and user agent across consent record API layers
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:48 +04:00
Émile Ré
b4d8bd8de2 Add country code to consent records
Store the visitor's resolved country code alongside regulation in
cookie_consent_records so downstream surfaces can expose it.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:48 +04:00
Émile Ré
b4bb8715ee Adapt cookie banner UI and texts per regulation
The server now resolves regulation-specific translations
(opt-out notice for CCPA, simple notice when no regulation
applies) and remaps text keys before returning the config.
The client hides buttons whose text is empty, so the banner
layout adapts without client-side consent-mode logic.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:42 +04:00
Émile Ré
f695b90b19 Store detected regulation in consent records and expose it in SDK
Add a regulation column to cookie_consent_records so each consent
captures which privacy law was in effect. Thread the value from the
handler's geoloc resolution through the service into the DB insert.

On the SDK side, add a Regulation union type to BannerConfig and expose
it via a getter on CookieBannerClient and in the probo-ready event
detail so themed-banner consumers can adapt their UI per regulation.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:42 +04:00
Émile Ré
d5394317bb Add geolocation-based privacy regulation detection to cookie banner
Resolve the visitor's IP to a country code via the geoloc service and
map it to the applicable privacy regulation (GDPR, UK GDPR, FADP, CCPA,
PIPEDA, LGPD, LFPDPPP, POPIA, PDPA, PIPL, PIPA, APPI, DPDP, PDPL).
The regulation and its implied consent mode (OPT_IN / OPT_OUT) are
injected into the GET /config response so the SDK can adapt its behavior.

Also makes geoloc.Service self-contained: LookupCountry and IsPopulated
now manage their own DB connections instead of requiring callers to pass
a pg.Querier.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:42 +04:00
Émile Ré
d0141c8dcf Style
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 17:49:13 +04:00
Émile Ré
85fb899220 Change endpoint name
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 17:41:47 +04:00
Émile Ré
f046c2967e Add unified tracker detection backend
- TrackerType enum (cookie, local_storage, session_storage, indexed_db, script, iframe)
- TrackerPattern model with EXACT + PREFIX matching for all types
- DetectedTracker model with upsert on conflict
- ReportDetectedTrackers service method handling cookies, storage, and resources
- POST /detected-trackers endpoint on cookie-banner v1 API
- buildSnapshot() now reads from tracker_patterns (cookie type only)
- Entity types registered (89, 90)

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 17:28:56 +04:00
Émile Ré
5cdaddf8b1 Store cookie durations as max_age_seconds
Replace the free-form duration TEXT column with a nullable
max_age_seconds INTEGER on both cookies and cookie_patterns
tables. The SDK detector now sends raw seconds instead of
humanized strings, eliminating locale-dependent comparisons
in the pattern merge worker. Humanization happens at display
time in the widget and console UI.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-30 11:46:11 +04:00
Émile Ré
48606f34c1 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>
2026-04-30 11:46:10 +04:00
Émile Ré
b7a28573f7 Send SDK version as X-SDK-Version header
Move the cookie-banner SDK version from the POST consents
request body to a custom X-SDK-Version header sent on every
API call. The server now reads it from the header and the
CORS middleware allows it through preflight.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-27 16:03:03 +04:00
Émile Ré
d7eec08cd2 Add banner ID to probo_consent cookie
Include a `bid` field in the consent cookie so it explicitly
identifies which cookie banner it belongs to, making validation
direct instead of relying on the visitor ID as an implicit
discriminator. Existing cookies without `bid` self-heal on the
next load by falling through to the API fetch.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 18:48:40 +04:00
Émile Ré
3a3335f28a Parse ?lang query param in cookie banner config API
Pass the lang parameter from the request URL to the service
so the config response returns localized texts and category
names for the requested language.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-24 12:18:08 +04:00
Émile Ré
b5a8781816 Style
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-23 10:52:00 +04:00
Émile Ré
e52b7cc19e Add detected-cookies public REST endpoint for cookie auto-discovery
The JS SDK will override document.cookie to detect unknown cookies set
by the website and report them to a new POST /{bannerID}/detected-cookies
endpoint. Reported cookies are inserted into the "Uncategorised" category
and a draft version is created so the admin can review them.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-23 10:50:57 +04:00
Émile Ré
1ec8e475de Add UX for cookie banner management
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-20 17:47:04 +04:00
Émile Ré
e3ab373a0c Add trusted proxy middleware and simplify clientip
Strip forwarded headers (Forwarded, X-Forwarded-For, X-Real-Ip)
from requests originating from untrusted proxies at the HTTP
server level, reusing the existing proxy-protocol trusted-proxies
config. The clientip package is now a pure extraction helper;
context plumbing and middleware wrappers are removed.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:01 +04:00
Émile Ré
713623c7c0 Document enum parameter rule and fix call style
Add coredata guide section on using Go enum constants as named
SQL parameters instead of hardcoded string literals. Fix mixed
inline/multiline RenderJSON call in cookie banner handler.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:01 +04:00
Émile Ré
7a50537bf4 Extract clientip middleware and add jsonutil helpers
Move clientIP extraction into a reusable pkg/server/api/clientip
package with RFC 7239 Forwarded header support. Add
pkg/server/jsonutil with helpers for common HTTP error responses
(RenderForbidden, RenderInternalServerError, RenderNotFound,
RenderBadRequest) and use them in the cookie banner handlers.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:01 +04:00
Émile Ré
8ec434d67e Fix review comments on cookie banner API
Parse only the first IP from X-Forwarded-For to prevent
the full chain from bypassing anonymization. Add secondary
sort key for deterministic consent selection. Reject origins
with empty port suffix in the validator.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:00 +04:00
Émile Ré
30a86a91f1 Add cookie banner public API endpoints
Implement config, consent retrieval, and consent recording
endpoints for the JS SDK. IP addresses are anonymized (last
octet zeroed for IPv4, /48 mask for IPv6) before storage.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:00 +04:00
Émile Ré
c8586346be Review fixes
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:21:00 +04:00
Émile Ré
36062310be Add cookie banner REST API with per-banner CORS middleware
Introduce /cookie-banner/v1/{bannerID}/config endpoint for the JS SDK.
The custom CORS middleware validates each request origin against the
specific banner being requested, preventing cross-customer leakage.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 10:20:59 +04:00