Commit Graph

554 Commits

Author SHA1 Message Date
Émile Ré
684fadea3e Remove unused description column from common_third_parties
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>
2026-05-12 16:43:36 +04:00
Émile Ré
caba84fd74 Fix missing newline at EOF
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 16:43:36 +04:00
Émile Ré
361fe1d713 Add common_third_parties shared reference table
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>
2026-05-12 16:43:35 +04:00
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
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é
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é
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é
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é
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é
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é
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é
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
Émile Ré
a911fcc220 Add seconds duration translations to cookie banner
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:47 +04:00
Émile Ré
bd8ec12876 Update detector report URL path
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-11 14:52:47 +04:00
Émile Ré
dc6a06cda8 Move detectors into detectors/ folder
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>
2026-05-11 14:52:47 +04:00
Émile Ré
57e035c64e Add StorageDetector, ThirdPartyDetector, and Detector interface
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>
2026-05-11 14:52:47 +04:00
Émile Ré
8920ce3b3b Release @probo/cookie-banner/v0.3.1 2026-05-08 12:58:15 +04:00
Émile Ré
5a80073356 Reopen correct component based on consent mode
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>
2026-05-08 10:32:45 +04:00
Émile Ré
901f715b7b Make headless buttons self-hide per regulation
ProboRejectButton and ProboCustomizeButton now auto-hide when
their corresponding text key is empty in the server-provided
config, removing the need for headless SDK consumers to
implement regulation-aware layout logic themselves.

The redundant applyLayout() in ProboThemedBanner is removed
since the headless components handle visibility directly.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-08 10:23:41 +04:00
Émile Ré
ad90fab080 Release @probo/cookie-banner/v0.3.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 16:24:32 +04:00
Émile Ré
1b2b72f29e Release @probo/n8n-nodes-probo/v0.183.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 16:24:17 +04:00
Émile Ré
806bd672ed Surface regulation and country code enums across API layers
Add proper enum types for Regulation and CountryCode in GraphQL
(with @goModel/@goEnum directives) and MCP (as standalone reusable
schemas with $ref). Update CLI, console UI, and n8n to include
the new fields.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:48 +04:00
Émile Ré
72fa79629f Review fixes
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:43 +04:00
Émile Ré
ca15a355e8 Defer banner button validation until config is loaded
The validation now runs on probo-ready instead of connectedCallback
so it can check config.texts to decide which buttons are required
for the active consent mode.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-07 15:13:42 +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é
4598506076 Extract shared types and add Regulation type with parsing methods
Move cookie banner types (CookieItem, Category, Regulation, BannerConfig,
etc.) into a dedicated types.ts file. Add a coredata.Regulation type with
parsing, JSON marshaling, and database scanning methods. Hardcode the
geoloc-import data directory since the submodule path is fixed.

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
Bryan Frimin
5274937e61 Release @probo/n8n-nodes-probo/v0.182.0
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-05-06 16:54:36 +02:00
Bryan Frimin
ac526fb54d Split release into per-track packages
Each shippable artifact (prb, probod server group, probod-bootstrap,
@probo/n8n-nodes-probo, @probo/cookie-banner) now has its own version
file, its own CHANGELOG.md, its own annotated-tag scheme of the form
<track>/v<version>, and its own GitHub Actions release workflow. The
unified release.yaml is removed; the unified CHANGELOG.md becomes a
short index pointing at each per-track file, with the prior history
preserved in CHANGELOG.archive.md.

Probod's CHANGELOG carries the post-split monorepo releases (0.174.0
through 0.181.0) so the server-group history stays continuous and the
probod docker image keeps its existing version line.

contrib/claude/release.md is split into contrib/claude/release/ with
one entrypoint per track plus a README that drives the agent: detect
which tracks have user-facing commits since their last tag and skip
tracks with no relevant changes, so a release request never tags an
unchanged track. The cookie-banner and n8n-node entrypoints add an
explicit npm run build step after the version bump (build.mjs bakes
package.json's version into __SDK_VERSION__) so compile errors and
package-lock.json updates are caught before tagging.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-05-06 16:04:36 +02:00
Bryan Frimin
5e55c888c4 Add Microsoft 365 SCIM bridge and access review driver
Microsoft 365's native SCIM endpoint is unreliable, so mirror the
Google Workspace bridge over Microsoft Graph: a new MICROSOFT_365
OAuth2 connector, a SCIM bridge provider listing /v1.0/users with
$select pagination, and an access review driver that derives admin
status from /directoryRoles members. Refactor the bridge runner to
share OAuth2 plumbing across providers and surface the new bridge
type, scopes, UI card, and bootstrap env wiring.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-05-06 15:41:30 +02:00
Sacha Al Himdani
1434d8336c Allow publishing generated documents as minor versions
Generated documents (asset list, risk register, SoA, ...) previously
only ever produced a new major version. Every regeneration of an
auto-built register consumed a major number, even when the change was
trivial. They now accept a minor flag and publish as
currentMajor.currentMinor+1 when set, bypassing the approval flow.

To carry the flag through cleanly, the document publish API was
refactored. The three split mutations (publishMajor, publishMinor,
requestDocumentVersionApproval) and the two bulk variants collapse
into a single publishDocument / bulkPublishDocuments, both taking the
new minor: Boolean! and a now-required changelog: String!. The same
shape flows through the CLI ("prb document publish --minor"), the MCP
tool, the n8n operations, and the Relay dialogs, where each
generated-doc dialog gains a "Publish as minor" button. Publishing
minor without an existing major is rejected with
ErrCannotPublishMinorWithoutMajor.

This is a deliberate breaking change for callers of the prior
mutations.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-06 14:02:59 +02:00
Émile Ré
af6e420f54 Remove cookie_patterns legacy, migrate to tracker_patterns
Delete coredata.CookiePattern and all associated CRUD methods,
rename shared types (CookiePatternOrderField, CookiePatternFilter,
CookiePatternMatchType) to TrackerPattern equivalents, and migrate
all API surfaces (GraphQL, MCP, CLI, n8n) to tracker_pattern naming.

The worker was already migrated in the base branch; this commit
completes the removal by dropping the old GraphQL schema/resolvers,
service methods, CLI commands, and n8n operations that operated on
the legacy cookie_patterns table.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-06 12:26:12 +04:00
Émile Ré
54655c218b Surface last_matched_at on all API surfaces
Add lastMatchedAt to the CookiePattern GraphQL type, MCP schema,
CLI view/list commands, and n8n get/getAll operations. Run
codegen to regenerate gqlgen models and MCP types.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-05 11:06:53 +04:00
Sacha Al Himdani
553901e4ad Add risk publish to document system
Replace the old snapshot-based system for risks with the publish
document system, mirroring the prior vendor / processing activity / DPIA
/ TIA migration. Includes the GraphQL mutation, MCP tool, CLI command,
n8n operation, frontend publish dialog, e2e tests, and a prosemirror
register template covering name, description, category, treatment,
owner, inherent and residual scoring, and notes.

The risk register lives as a generated DocumentTypeRegister document on
the organization, reused across publishes (the major version bumps on
every republish). Approvers can be passed in to create a draft pending
approval; otherwise the version is published immediately. The frontend
Risks page exposes a Publish button and a Document link button when the
document exists, and pre-fills the previous default approvers.

Risks was the last remaining snapshot type, so this commit also removes
the entire snapshot system: drop snapshotId from the Risk GraphQL type
and RiskFilter; remove RiskSnapshotter, Risks.Snapshot,
InsertRiskSnapshots, and the SnapshotID/SourceID fields on Risk; delete
Snapshot, ControlSnapshot, SnapshotsType, SnapshotOrderField,
Snapshottable, the SnapshotService, the Snapshot console resolvers and
GraphQL schema, the Snapshot MCP types and operations
(list/get/take/listControlSnapshots), the snapshot CLI (prb snapshot),
the snapshot frontend pages, routes, banner, LinkedSnapshotsCard,
SnapshotGraph, snapshot helpers, and the snapshot n8n resource and
control link/unlink snapshot operations. The snapshot_id columns remain
in the database but are now filtered out with snapshot_id IS NULL.

Add Get/Upsert/Clear GeneratedDocumentID methods on Risk backed by a new
risks_document_id column on generated_documents, matching the
ProcessingActivity/Finding/Vendor pattern. The migration command
migrate-risk-snapshots-to-documents uses raw SQL queries instead of the
Go snapshot types, since those are gone.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-04 14:13:42 +02:00
Émile Ré
2ceb8f4126 Fix n8n cookieConsentRecord getAll operation
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 22:13:32 +04:00
Émile Ré
3e8cd8444a Bump @probo/cookie-banner to 0.2.0
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 21:24:39 +04:00
Émile Ré
a83d313dfa Clear pending queue before stopping on 404
When flush receives a 404, stop() would re-flush remaining
pending cookies to a dead endpoint. Clear the queue first to
avoid the wasted request.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:42:29 +04:00
Émile Ré
33940d63ca Run cookie detection regardless of banner state
Decouple detection from the banner's active state so admins
can discover cookies before making the banner visible. The
client now starts the detector even when the config endpoint
returns 404, and the detector stops itself if the report
endpoint returns 404 (wrong or deleted banner ID).

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 19:01:13 +04:00
Émile Ré
71cad5f15e Allow clearing optional fields in n8n cookie updates
The previous fix prevented unintentional clearing of
`privacyPolicyUrl` and `maxAgeSeconds` by skipping the field
when its value was falsy, but this also removed the user's
ability to explicitly clear an existing value.

Move both fields into an `Additional Fields` collection so we
can distinguish between "not provided" (skip) and "provided
empty" (clear), matching the existing pattern in
`vendor/update.operation.ts`.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-02 15:35:20 +04:00
Émile Ré
1650b26fa9 Fix destructive update behavior in n8n cookie operations
Avoid clearing existing values when optional fields are left at their
defaults: only send privacyPolicyUrl/maxAgeSeconds when truthy, filter
empty entries from gcmConsentTypes, and constrain consentExpiryDays to
positive integers on create.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 20:11:37 +04:00
Émile Ré
1996545125 Add excluded field to cookie pattern operations
Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 17:59:11 +04:00
Émile Ré
6f849b36a1 Add cookie banner operations to n8n node
Adds four new resources (cookieBanner, cookieCategory,
cookiePattern, cookieConsentRecord) covering all mutations
and queries from the cookie banner GraphQL resolvers.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-01 17:56:23 +04:00