Each scope card now shows a flowchart of its nodes, processes, and
threats, with a distinct shape per type: stadium for entities,
hexagon for boundaries, rectangle for assets, cylinder for data, and
a red hexagon for threats attached via dashed edges to their process
target. The Mermaid source is built on the backend and exposed as a
new `mermaid` field on RiskAssessmentScope; the frontend just renders
it via @probo/ui's MermaidDiagram and shows a copy button + legend.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
When `_ready` was already true, `subscribe` called `cb` directly and
then `onReady` called it again, producing duplicate side effects.
Remove the redundant direct call since `onReady` already handles the
immediate-dispatch case.
Signed-off-by: Émile Ré <emile@getprobo.com>
getAll() now returns a cached snapshot that is only replaced on state
changes, making it safe for useSyncExternalStore. The new subscribe()
method unifies onReady + onChange with immediate replay so React
consumers get a single subscription point.
Signed-off-by: Émile Ré <emile@getprobo.com>
Expose a ConsentManager singleton via `@probo/cookie-banner/consent`
(ESM) and `window.Probo.consent` (IIFE) so customers can check and
react to consent state in their bundled JavaScript code, solving the
problem of third-party SDKs initialized programmatically that cannot
be blocked via data-cookie-consent attributes.
Signed-off-by: Émile Ré <emile@getprobo.com>
- Replace copy-pasted New Relic URLs in Resend third-party entry
with correct resend.com URLs and drop inapplicable fields
- Escape single '%' instead of '%%' in LIKE pattern conversion so
literal percent signs are not treated as wildcards
- Return actual row ID from CommonTrackerPattern.Upsert via
RETURNING id so conflict-path callers get the existing ID
- Add ORDER BY id ASC to vendor-by-common-third-party query for
deterministic LIMIT 1 selection
Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
Seed common_tracker_patterns from Open Cookie Database (2300
patterns with max_age_seconds where available). Add domain
aliases to all 92 vendors in data.json. Extend third-parties
import to upsert domain aliases. Add dedicated import command
for tracker patterns with third-party name cache.
Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
- Require CLIENT_SECRET for new access-review connectors
- Use Heroku account UUID as ExternalID
- Bump GitHub orgs picker to per_page=100
- Drop Snyk, Ramp, Lever, Deel access-review providers
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
- Wire post-OAuth pickers and 2-auto callback handling
- Add 13 vendor logo components for new connectors
- Wire access-review connectors into bootstrap config
- Track e2e gap for new access-review connectors
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Renames the user-facing 'vendor' concept to 'third party' across the
entire codebase. The shared common_third_parties reference table is
unchanged.
Migration. Renames the vendor_category enum, the vendors and
vendor_<entity> tables (contacts, services, compliance_reports,
business_associate_agreements, data_privacy_agreements,
risk_assessments) and their vendor_id columns, the asset_vendors /
data_vendors / processing_activity_vendors junction tables,
generated_documents.vendors_document_id, the webhook_event_type
'vendor:<verb>' values, and the snapshots_type 'VENDORS' value.
Backend. Renames coredata models and SQL queries, probo services,
GraphQL / MCP API surface, console / trust / webhook resolvers and
types, the CLI (prb vendor* -> prb third-party*; pkg/cmd/vendormgmt
-> pkg/cmd/thirdpartymgmt), the document generator, vetting agent
prompts, and the common-third-parties-import command.
Frontend, packages, n8n, e2e. Renames apps/console pages, components,
hooks, routes, dialogs, and tabs; the shared @probo/vendors package
(now @probo/third-parties); the @probo/ui Vendors atoms (now
ThirdParties, VendorLogo -> ThirdPartyLogo); the n8n community node
actions/vendor folder (now actions/thirdParty); and the e2e Go test
suite (console and MCP). Filesystem and URL paths use kebab-case
(third-parties), GraphQL fields and TypeScript identifiers use
camelCase (thirdParty / thirdParties), Go types use PascalCase
(ThirdParty), and human-facing text uses 'third party' with a space.
Co-authored-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
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>
When visitorId is null (no localStorage entry), the client now
skips both the cookie check and the /consents/:id fetch that
was always returning 404 for first-time visitors. The visitor ID
is created lazily on first consent action instead.
Signed-off-by: Émile Ré <emile@getprobo.com>
- relay: key uploadables map by actual variable name instead of iteration
index so order-mismatch between Object.keys passes can't desync the
multipart map from form field names
- mcp/v1: drop dead commented middleware line
- DurationPicker: tighten parse regex to require PT prefix for M/H and P
for D/W, and reject NaN in stringify so cleared inputs don't produce
invalid duration strings
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
The description field was never surfaced in the UI and added no value.
Drop it from the database, Go structs, GraphQL schema, import tool,
frontend fragment, and vendor seed data.
Signed-off-by: Émile Ré <emile@getprobo.com>
Introduce a globally-shared, non-tenant-scoped common_third_parties
table that mirrors the public subset of vendor metadata, plus a
one-shot cmd/common-third-parties-import CLI that seeds it from
packages/vendors/data.json. The catalog will back future flows (e.g.
vendor autocomplete) so each tenant no longer needs to duplicate the
same baseline data.
The importer is idempotent via ON CONFLICT (lower(name)) DO UPDATE
and prints inserted/updated counts. GIDs use gid.NilTenant since the
table is not tenant-scoped; uniqueness still comes from the entity
type plus 14 bytes of timestamp/random suffix.
Signed-off-by: Émile Ré <emile@getprobo.com>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
Reorganize detector files into src/detectors/ following the same
pattern as src/integrations/. Rename detector-interface.ts to
detector.ts (the interface), and detector.ts to cookie-detector.ts.
Barrel export from detectors/index.ts provides Detector interface
and all three detector classes.
Signed-off-by: Émile Ré <emile@getprobo.com>
Introduce a common Detector interface (start/stop) implemented by
CookieDetector, StorageDetector, and ThirdPartyDetector. The client
manages them as a uniform array, simplifying lifecycle management.
StorageDetector wraps Storage.prototype.setItem and indexedDB.open
to detect localStorage, sessionStorage, and IndexedDB usage.
ThirdPartyDetector uses MutationObserver to detect cross-origin
script and iframe elements, reporting at origin level.
Both report to POST /detected-trackers with 2s debounce and max
100 items per batch.
Signed-off-by: Émile Ré <emile@getprobo.com>
OPT_OUT regulations (e.g. CCPA) now reopen the banner instead
of the preference panel when clicking the settings widget,
since users only need Accept/Reject choices rather than
granular per-category toggles.
Signed-off-by: Émile Ré <emile@getprobo.com>