Commit Graph

4084 Commits

Author SHA1 Message Date
Sacha Al Himdani
ee22e9f964 Address CodeQL findings: unused imports, missing semicolons, dead conditionals
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 14:09:35 +02:00
Sacha Al Himdani
d82df6b8ee Sanitize MCP errors to avoid leaking internal details
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 13:37:51 +02:00
Sacha Al Himdani
4f88b15f6d Bump mermaid to 11.15.0 to fix moderate security advisories
Fixes GHSA-6m6c-36f7-fhxh (Gantt infinite loop DoS), GHSA-xcj9-5m2h-648r
and GHSA-87f9-hvmw-gh4p (CSS injection via classDefs/configuration), and
GHSA-ghcm-xqfw-q4vr (HTML injection via classDef in state diagrams).

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 13:07:28 +02:00
Émile Ré
95271e3f4e Release probod/v0.186.1
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 14:25:20 +04:00
Émile Ré
150f5d9c46 Fix wrong entity types in tracker_patterns and detected_trackers GIDs
tracker_patterns rows were created with entity type 88 (removed
CookiePatternEntityType) instead of 89 (TrackerPatternEntityType), and
detected_trackers rows migrated from the cookies table carried entity
type 85 (removed CookieEntityType) instead of 90 (DetectedTrackerEntityType).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 12:28:18 +04:00
Sacha Al Himdani
97c902ddf1 Release probod/v0.186.0
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 10:10:59 +02:00
Sacha Al Himdani
cc185f66dd Release prb/v0.185.0
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 10:08:28 +02:00
Sacha Al Himdani
d88ae3288b Add pg pool tuning options from kit v0.10.0
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 10:04:13 +02:00
Sacha Al Himdani
51a8380475 Update kit to v0.10.0
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 09:48:53 +02:00
Émile Ré
a07ced47ae Release @probo/cookie-banner/v0.4.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 10:51:26 +04:00
Émile Ré
d1a2a7cf32 Release @probo/n8n-nodes-probo/v0.184.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 10:50:55 +04:00
Émile Ré
d7f27e73d7 Release probod/v0.185.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 10:50:29 +04:00
Émile Ré
b6615d4279 Release prb/v0.184.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 10:49:55 +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é
8a1774f91c Fix go-lint
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 18:08:55 +04:00
Émile Ré
6bf1e54a50 Fix SQL corruption in FindMatchingPattern by using strings.Replace
fmt.Sprintf interprets the literal % characters in the LIKE escape
clause as format verbs, corrupting the query and causing a 500 on
the /report endpoint.  Reorder tracker type / source filters in the
trackers page.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 17:42:51 +04:00
Émile Ré
10313069c8 Remove tracker pattern row updatedAt column
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 16:36:47 +04:00
Émile Ré
5e6220a67c Detect variable tokens in tracker pattern names
The pattern analysis worker now recognises UUID-like, hash-like,
and long numeric tokens as variable parts and replaces them with
wildcards heuristically, even from a single observation. This
prevents site-specific identifiers from being treated as static
text while meaningful suffixes (window_id, posthog, …) get
incorrectly wildcarded.

Also upgrades globMatch and the FindMatchingPattern SQL query
to support multiple wildcards in a single pattern.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 16:36:15 +04:00
Émile Ré
71a33e412b Add tracker type filter and color-coded badges
The trackers page now lets users filter by tracker type
(Cookie, localStorage, sessionStorage, IndexedDB, Cache
Storage) in addition to the existing source filter. Each
tracker type and cookie source badge uses a distinct color
for quick visual scanning.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 15:34:24 +04:00
Émile Ré
4aebf65b89 ui: stop importing phosphor icons with bare-name aliases
Add a rule to contrib/claude/ui.md requiring the Icon-suffixed exports
(e.g. EyeIcon) instead of aliased bare names (Eye as IconEye), and fix
TrackerResourceRow to follow it.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:56:34 +04:00
Émile Ré
b81795e62e cookie-banner: attempt sendBeacon even when async flush is in-flight
The early return from flushSync() when this.flushing was true skipped
the synchronous sendBeacon path, dropping pending reports during page
unload. Move the flushing guard after the sendBeacon attempt so the
unload-safe transport is always tried; only the keepalive-fetch
fallback is gated on the mutex.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:57 +04:00
Émile Ré
733b793991 e2e: exercise the report endpoint in tracker resource test
The previous test seeded tracker resources directly via the
GraphQL `CreateTrackerResource` mutation, which doesn't go
through the public `/report` handler. Switching the seeding to
`ReportDetectedResources` covers the end-to-end path -- payload
decoding, validation, and the new upsert -- so the listing assert
is now grounded in the same write path that real clients use.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:57 +04:00
Émile Ré
f8fad78d0c cookie-banner: keep flush entries until delivery is confirmed
flushSync() previously deleted pending entries unconditionally
once a transport accepted the payload. That dropped data when
the page was restored from bfcache after visibilitychange:hidden,
and it allowed concurrent flushes to take overlapping batches.

Now items are only removed from `pending` once the transport
confirms delivery: synchronously for sendBeacon, in the keepalive
fetch's `.then` on an `ok` response. Failed fetches leave entries
queued so the next flush can retry, and a `flushing` guard prevents
overlapping in-flight batches.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:57 +04:00
Émile Ré
31870495a1 cookiebanner: rename DetectedTracker.InsertIfNotExists to Upsert
The method uses ON CONFLICT ... DO UPDATE, so the name now
matches the actual behaviour. TrackerPattern.InsertIfNotExists
keeps its name because it uses ON CONFLICT ... DO NOTHING.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
438ca439de cookie-banner: rename ThirdPartyDetector to ResourceDetector
The detector emits tracker_resource entries (scripts, iframes, images,
beacons, fonts, fetches, media, service workers); the "third-party"
qualifier was misleading because the same code path also fires for
service workers, which are always same-origin per browser security
rules. Rename the class and file to match what they actually produce.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +04:00
Émile Ré
bec0254597 cookie-banner: share one ReportQueue across detectors
The three detectors (cookies, storage, resources) each duplicated the
same debounce, batch, retry, and NotFoundError plumbing and each fired
its own POST /report despite the server already accepting a unified
{cookies, storage, resources} payload. Collapse the three sender paths
into a single ReportQueue so a 2 s debounce window produces one request
instead of up to three, dedup is centralised behind type-namespaced
keys (c:/s:/r:) that cannot collide across detectors, and a tab-close
drain via sendBeacon (with keepalive fetch fallback) saves the last
debounce window of detections that previously vanished on unload.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:56 +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é
8e2b8e6288 Fix JS lint
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é
04fdaed772 cookie-banner: clean up StorageDetector restore-on-stop
localStorage and sessionStorage share Storage.prototype, so there is
only one setItem to wrap and one to restore. The previous code kept a
never-assigned originalSessionSetItem field and a dead second if branch
that restored from a field already nulled out a few lines above.

Collapse the two fields into a single originalSetItem and drop the dead
branch. No behavioural change.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
1abb542c2e tests: fix tracker_pattern SCRIPT case, add tracker_resource e2e tests
- Fix tracker_pattern_test.go: replace SCRIPT (removed from enum)
  with LOCAL_STORAGE.
- Add tracker_resource_test.go with full CRUD, RBAC (viewer denied),
  cross-banner mismatch, and list coverage.
- Add CreateTrackerResource factory helper.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:55 +04:00
Émile Ré
8911aa16d7 frontend: add TrackerResource resources page
Add configuration/resources/ page for managing tracked scripts and
iframes. Includes page/loader/skeleton, TrackerResourceRow with
inline edit, delete/move/exclude mutations, search + type filter,
and LAST_DETECTED_AT DESC default ordering. Register route and tab.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:54 +04:00
Émile Ré
030fdf4be3 frontend: rename detection page to trackers, drop SCRIPT/IFRAME branches
- Rename configuration/detection/ to configuration/trackers/.
- Rename files: CookieBannerDetectionPage* → CookieBannerTrackersPage*,
  DetectionPatternRow* → TrackerPatternRow*.
- Update GraphQL operation/fragment names and connection keys.
- Update route path "detection" → "trackers" and tab label.
- Remove SCRIPT/IFRAME cases from trackerTypeLabel() switch.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:54 +04:00
Émile Ré
36dbe47192 cli: add tracker-resource commands
Add pkg/cmd/tracker-resource/ with list, view, create, update, delete,
and move subcommands mirroring the tracker-pattern CLI surface.
Register in pkg/cmd/root/root.go.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:54 +04:00
Émile Ré
5bf6a67c06 mcp: add TrackerResource tools and drop SCRIPT/IFRAME from tracker_type
- Remove SCRIPT/IFRAME from the tracker_type enum in the MCP spec.
- Add TrackerResource schema in components/schemas.
- Add 6 tools: listTrackerResources, getTrackerResource,
  addTrackerResource, updateTrackerResource, deleteTrackerResource,
  moveTrackerResourceToCategory with input/output schemas.
- Add types/tracker_resource.go helper and resolver implementations.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:54 +04:00
Émile Ré
663dc7478f graphql: add TrackerResource schema, resolvers, and IAM actions
Add the full GraphQL surface for the new tracker_resources table:

- TrackerResourceType enum (SCRIPT, IFRAME), TrackerResource node type
  with connection/edge/order/filter, fields on CookieBanner
  (uncategorisedTrackerResources) and CookieCategory (trackerResources).
- Mutations: createTrackerResource, updateTrackerResource,
  deleteTrackerResource, moveTrackerResourceToCategory with
  inputs and payloads.
- Resolvers for all mutations, connection fields, field resolvers
  (cookieCategory, permission), and totalCount.
- IAM actions: core:tracker-resource:{get,list,create,update,delete}.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:54 +04:00
Émile Ré
6e07da9ab2 cookiebanner: add TrackerResource service layer
Wire resource ingestion and add full CRUD + list/count service methods
for the new tracker_resources table.

- reportDetectedResource splits the URL into origin/path and upserts
  into tracker_resources with the uncategorised category.
- CreateTrackerResource, GetTrackerResource, UpdateTrackerResource,
  DeleteTrackerResource, MoveTrackerResourceToCategory mirror the
  tracker-pattern service surface.
- ListTrackerResourcesForCategory, CountTrackerResourcesForCategory,
  ListUncategorisedTrackerResources, CountUncategorisedTrackerResources
  provide paginated access.
- Request structs with Validate() and dedicated error sentinels.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +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é
923488b2b0 Rename migrations
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +04:00
Émile Ré
8393e4bfb1 Fix unknown function ends_with in pg
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +04:00
Émile Ré
3e4b4eec78 Go fix
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:53 +04:00
Émile Ré
855de92e1f Update tests for GLOB match type
Rewrite worker unit tests: TestTemplateCandidates, TestGlobMatch,
TestSplitTokens, and updated TestFindMergeGroups with sandwich pattern
cases. Update e2e test to use GLOB instead of PREFIX.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +04:00
Émile Ré
0358aafb62 Update API surface to use GLOB match type instead of PREFIX
Replace PREFIX with GLOB in GraphQL enum, MCP specification, CLI
interactive prompts, and n8n node options.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +04:00
Émile Ré
d006d4e462 Implement glob-based pattern discovery and matching in worker
Replace prefix-only merge logic with token-template analysis that
discovers sandwich patterns (e.g. ph_phc_*_posthog). The worker now
emits GLOB patterns, adoption uses globMatch, and validation enforces
exactly one wildcard for GLOB patterns.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +04:00
Émile Ré
70d86d68af Add GLOB match type to replace PREFIX for tracker patterns
Introduces a wildcard-based match type that supports prefix, suffix,
and sandwich patterns (e.g. ph_phc_*_posthog). The SQL matching uses
starts_with/ends_with on the parts split at '*', avoiding LIKE and
its underscore escaping issues. Existing PREFIX rows are migrated to
GLOB with a trailing '*'.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +04:00
Émile Ré
31bfbefc45 Make tracker pattern displayName read-only
The displayName field was always predictable from pattern + matchType
and allowing edits added unnecessary complexity. Remove displayName
from UpdateTrackerPatternInput across all surfaces (GraphQL, MCP, CLI,
n8n) and make the frontend show it as non-editable text.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:52 +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