Commit Graph

909 Commits

Author SHA1 Message Date
Cursor Agent
600aa6b8a8 Remove obsolete minor publish error
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-26 08:26:49 -07:00
Cursor Agent
b721179974 Allow initial document minor publishing
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-26 08:26:47 -07:00
Sacha Al Himdani
6dfdd7ca49 Link measures to third parties
Add a many-to-many relationship between measures and third parties,
surfaced as a measures tab on the third party detail page and a third
parties tab on the measure detail page. Each side gets a paginated
list with a link/unlink dialog.

Also remove the right-hand drawer on the measure detail page and
expose the state as a badge in the page header, mirroring how the
compliance page surfaces its active flag.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-26 15:49:17 +02:00
Sacha Al Himdani
b6b1e801b1 Add third-party self-referential relations
Introduce a self-referential many-to-many relation table so a
third party can have child third parties. Each relation is
directional (parent to child); both directions can coexist as
independent rows.

Add a first_level boolean on third_parties (default true) with
a filter on the list page that defaults to showing only
first-level third parties.

Frontend adds a "Third Parties" tab on the detail page where
users can link existing third parties or create new ones from
the common third party catalog (created as non-first-level).
The list page gets a First Level/All toggle filter.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-26 15:21:38 +02:00
Émile Ré
1c3ce56b48 Mark page-world extension writes with EXTENSION source
The previous cleanup deleted every isExtensionCaller() site, including
the one in cookie/storage detectors that did fire reliably for the
residual case: page-world extensions (MV3 main world, userscripts with
@grant none) whose stack contains a chrome-/moz-/safari-web-extension
frame at the synchronous write. Recover that signal for free by
returning fromExtension from getInitiatorURL (it already walks the
stack and discards extension frames via continue), and have the cookie
and storage detectors report source: "extension" instead of "script"
when the flag is set.

End-to-end plumbing reuses the existing source column: extend the
cookie_source Postgres enum with EXTENSION, add the CookieSourceExtension
constant with a doc block describing each bucket's actual semantics,
add the handler.go switch cases, expose EXTENSION on the GraphQL and
MCP CookieSource enums, and add the Extension option to the console
source filter.

Update bestSource in the pattern analysis worker so a glob merging
only extension-attributed exact patterns is no longer silently rolled
up to PRE_EXISTING. New precedence is SCRIPT > EXTENSION > PRE_EXISTING,
matching the upsert SQL's "page-script wins" rule and the asymmetric
signal strength of each bucket.

Out of scope: any behavioural use of EXTENSION (auto-exclusion,
denylist classification, dashboard surfacing) -- that belongs in the
follow-up backend denylist plan.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:48 +02:00
Émile Ré
e2b8ee15e7 Track source on detected storage trackers
The cookie detector tags every detection with a source (script,
pre-existing, http) but the storage detector did not, so storage
rows always landed in detected_trackers with source NULL even though
the SDK already distinguishes wrapper writes from pre-load scans.

Plumb a "script"/"pre-existing" source from the storage detector
through the report endpoint into DetectedStorageItem, so the column
gets populated for localStorage, sessionStorage, indexedDB and
cacheStorage entries. No schema change is needed: detected_trackers
already accepts CookieSource values regardless of tracker_type, and
the existing row renders the badge as soon as it is non-null.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 11:14:47 +02:00
Bryan Frimin
23af4caf43 Fix empty string as "no attributes"
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:42:38 -07:00
Bryan Frimin
071bd17f43 Fix mcp server
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:38 -07:00
Bryan Frimin
392f81bd74 Enforce IAM authorization on every console resolver
Audited pkg/server/api/console/v1 for resolvers that touched tenant
data without calling r.authorize, batchAuthorize, or Permission. Closed
every gap so every data-bearing field goes through IAM (and produces an
audit log entry when an organization_id is present).

* High-severity reads now authorize: accessSourceResolver.Connector and
  ConnectionStatus, controlResolver.Regulatory/Contractual/RiskAssessment,
  electronicSignatureResolver.CertificateFileURL/Events,
  commonThirdPartyResolver.LogoURL, and the proper
  accessSourceResolver/accessReviewCampaignResolver/auditLogEntryResolver
  Organization resolvers (authorize + dataloader load, fixing the latent
  empty-name bug from the previous force-resolver no-op implementations).
* TotalCount/DetectedCount aggregates now authorize the matching list
  action across access review, audit log, statement of applicability,
  detected tracker, tracker pattern, and tracker resource connections.
* queryResolver.CommonThirdParties authorizes against the principal's
  identity via the new identity-scoped CommonThirdPartyCatalogPolicy.
* Add ActionCommonThirdPartyGet/List, ActionElectronicSignatureGet probo
  action constants; wire ActionElectronicSignatureGet into ViewerPolicy
  and AuditorPolicy.
* Implement AuthorizationAttributes on CommonThirdParty (no org) and
  ElectronicSignature (organization_id) so the authorizer can resolve
  attributes for the new actions.
* Delete the dead "type AccessReview" GraphQL type (no Go constructor,
  no frontend reference) and drop its orphan resolver bundle.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:38 -07:00
Bryan Frimin
de325af4d9 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:37 -07:00
Bryan Frimin
7405565699 Use authorize-returned scope in Node resolver
The Node resolver was manually constructing a scope from the object
ID, both at the top of the function and again inside several closures.
Since r.authorize already returns the resolved scope, pass it into the
loadNode closures and drop the duplicate construction.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:37 -07:00
Bryan Frimin
5e9ff656d3 Add per-request Authorize dataloader to console v1
Resolving a typical Console GraphQL query triggers many parallel
authorize calls (one per resource per field resolver). This commit
collapses them via a dataloader: parallel calls within the same
request are gathered into a single iam.Authorizer.AuthorizeMulti pass,
and only fall back to per-item Authorize when AuthorizeMulti rejects
the whole batch (e.g. mixed organizations).

The loader key encodes resource id, action, options, and a canonical
JSON-encoded attribute map so logically identical calls share a key
while differing ones do not. The loader is created without caching so
repeated calls within a request still produce one audit log entry per
call. dataloader.NewAuthorizeFunc preserves the existing
authz.AuthorizeFunc signature and error mapping.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:36 -07:00
Bryan Frimin
a862faee39 Wire batch authorize into server APIs
Add authz.NewBatchAuthorizeFunc — the batch counterpart to the
existing AuthorizeFunc — together with WithBatchAttr,
WithBatchSkipAssumptionCheck, and WithBatchDryRun options. It maps the
new batch errors (mixed organization, empty batch, unsupported
resource type) to GraphQL Invalid responses, and reuses the existing
mappings for ErrAssumptionRequired / ErrInsufficientPermissions /
ErrResourceNotFound.

Plumb the new function into the Connect and Console resolvers and add
Resolver.AuthorizeBatch to the MCP resolver with equivalent error
mapping for tool callers.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:36 -07:00
Bryan Frimin
0c5168b5c6 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:35 -07:00
Bryan Frimin
8bf7be0c0d Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:34 -07:00
Bryan Frimin
4d1a98ffdb Create scope in Authorize
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:34 -07:00
Bryan Frimin
7f59a0d2ee Remove MustAuthorize pattern
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-23 13:31:34 -07:00
Sacha Al Himdani
83445c6e34 Fix signature count mismatch with signatures tab on documents
The badge on a document version showed signatures filtered by
activeContract: true, while the signatures tab fetched signatures with
no filter and listed people filtered by contractEnded: false and
state: ACTIVE. The two views disagreed both when a signer's contract had
ended and when a signer was deactivated while still under contract.

Add a state: ProfileState field to DocumentVersionSignatureFilter
alongside the existing activeContract filter, so the signature query
can mirror the same predicates as the people query. Pass
{ activeContract: true, state: ACTIVE } from the badge, the document
list item, and the signatures tab fragment. The same filter is now
evaluated on both the count and the list.

Threaded through the console and MCP resolvers, the MCP spec, and the
n8n getAllSignatures operation.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-22 14:34:35 +02:00
Émile Ré
03a5f6c1d4 Fix lint
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 11:58:02 +02:00
Émile Ré
b46f2656f5 Add tracker pattern detail page with properties and detected trackers sections
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 11:54:44 +02:00
Émile Ré
f8debf5406 Always instantiate coredata CookieCategoryFilter to avoid nil pointer risks
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 10:33:51 +02:00
Émile Ré
5abd670707 Update console tracker page
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 10:20:07 +02:00
Émile Ré
be9b43e98f Unify cookie category queries with coredata filter
Replace duplicated LoadConsentCategoriesByCookieBannerID,
CountConsentCategoriesByCookieBannerID, and
LoadAllConsentCategoriesByCookieBannerID with a single
CookieCategoryFilter in pkg/coredata. The filter uses the
standard CASE WHEN idiom to optionally exclude a kind,
eliminating branching in the service layer.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-22 10:20:06 +02:00
Sacha Al Himdani
883031830f Add risk assessment system to CLI, MCP, and N8N
Expose the full risk assessment hierarchy (assessments, scopes, nodes,
processes, threats, scenarios) with CRUD operations and scenario
linking across all three interfaces.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-21 17:50:22 +02:00
Bryan Frimin
3e4a9be7c0 Remove tenant service pattern
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-20 16:51:15 -07:00
Bryan Frimin
30db98455d Uniformize enum style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-20 15:36:47 -07:00
Sacha Al Himdani
3e82b64f01 Fix wsl_v5 lint errors and enforce lint on PR CI
Add missing blank lines around if-block boundaries in two files
to satisfy wsl_v5, and make lint-go and lint-js fail the build on
pull requests (not only on push to main) by always running the
strict lint and using reviewdog purely for inline annotations.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-20 16:49:42 +02:00
Sacha Al Himdani
9e61e5e1b7 Return clear error on user delete FK violation
Deleting a membership profile referenced by other tables (owner,
approver, assignee, etc.) surfaced as a generic Internal error.
Detect the Postgres FK violation (23503) in the coredata Delete,
return ErrResourceInUse, and map it to CONFLICT in the GraphQL
and MCP resolvers so the client sees an actionable error.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-20 15:45:32 +02:00
Émile Ré
7ac77b85e9 Fix wsl_v5 lint errors and exclude node_modules from linting 2026-05-20 15:05:08 +04:00
Émile Ré
3b20f466c3 Fix indentation after errors.AsType refactor
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 11:53:50 +04:00
Émile Ré
f5703d390b Enforce Go style rules across codebase
Apply five style rules: convert iota string enums to typed
string constants, replace errors.As with errors.AsType,
merge three-group imports into two groups, fix multiline
parameter/argument formatting, and replace fmt.Sprintf URL
construction with net/url.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 11:46:39 +04:00
Émile Ré
9156d6a16a Add wsl linter and fix
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 09:27:28 +04:00
Sacha Al Himdani
eedfdcecc8 Render mermaid diagram per risk assessment scope
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>
2026-05-19 21:44:28 +02:00
Sacha Al Himdani
b9262b5150 Add risk assessment system
Introduce a hierarchical risk assessment model with six entity types:

- Risk Assessment: top-level container scoped to an organization
- Risk Assessment Scope: sub-container for scoping threat modeling
  exercises within an assessment
- Risk Assessment Node: DFD elements typed as ENTITY, BOUNDARY,
  ASSET, or DATA within a scope
- Risk Assessment Process: directed data flows between two nodes
- Risk Assessment Threat: descriptive threats attached to a process
  with a free-text category (e.g. Confidentiality, Integrity)
- Risk Scenario: thin join linking a threat to a risk from the
  register, carrying only a name and description

Risk scoring (likelihood, impact, treatment) remains on the existing
Risk entity. Threats are purely descriptive. Risk Scenarios connect
the threat model to the risk register without duplicating scores.

Backend: migration with PG enum for node types, coredata structs,
service layer with full CRUD and validation, GraphQL schema with
18 mutations and paginated connections, authorization actions and
policies, and base_resolvers.go Node dispatch for all entity types.

Frontend: Risk Assessments list page with create dialog, detail page
showing scopes as cards with nodes/processes/threats tables, inline
create/edit/delete actions on all entities, and a Scenarios tab on
the Risk detail page linking threats to risks. Existing RiskGraph.ts
hook file removed in favor of colocated queries in page files.

E2E tests cover CRUD for all entity types, RBAC, and tenant
isolation.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-19 21:44:27 +02:00
Cursor Agent
5995f41fa0 Remove backend inactive profile validation
Users are created with state=INACTIVE and only become ACTIVE on
first login. The server-side state check incorrectly rejected
newly-created users who hadn't yet activated their accounts.

The frontend filter (state: ACTIVE) is the correct layer to hide
deactivated people from the recipient picker. The existing contract
end date validation remains as server-side defense in depth.

Co-authored-by: Émile Ré <nemile.re@gmail.com>
Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 17:03:54 +04:00
Cursor Agent
e4005e0f74 Filter inactive people from signature request recipients
The signature request recipient lists (both the multi-select dialog
and the document signatures page) included people who were inactive
via SCIM deactivation. The existing filter only excluded people with
ended contracts but not those with an INACTIVE state.

This adds state: ACTIVE to the ProfileFilter in both frontend queries
and introduces a server-side ErrProfileInactive validation in the
RequestSignature and BulkRequestSignatures service methods to reject
inactive profiles even if called directly via API.

Co-authored-by: Émile Ré <nemile.re@gmail.com>
Signed-off-by: Émile Ré <emile@getprobo.com>
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-18 17:03:53 +04:00
Aurélien Sibiril
c2db47e698 Require CLIENT_SECRET for new access-review connectors → Drop Snyk, Ramp, Lever, Deel access-review providers
- 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>
2026-05-17 17:22:50 +02:00
Aurélien Sibiril
f3a745c245 Add List*Organizations helpers in pkg/accessreview/drivers → Ignore .oauth-credentials.txt
- Add List*Organizations helpers in pkg/accessreview/drivers
- Replace AccessSource picker switches with map dispatch
- Drop dead per-provider connector settings wrappers
- Ignore .oauth-credentials.txt

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-17 17:22:49 +02:00
Aurélien Sibiril
8917b46541 Move Vercel /v2/user fetch to pkg/connector/vercel.go → Migrate connector settings callers to generic
- Move Vercel /v2/user fetch to pkg/connector/vercel.go
- Authorize NeedsConfiguration and SelectedOrganization
- Add coredata.ConnectorSettings[T] generic helper
- Migrate connector settings callers to generic

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-17 17:22:49 +02:00
Aurélien Sibiril
caec9f1ad5 Rename access-review migration with random time → Validate PagerDuty subdomain on OAuth callback
- Rename access-review migration with random time
- Move PagerDuty token-response handling to its own file
- Strip OAuth error_description from log and redirect
- Validate PagerDuty subdomain on OAuth callback

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-17 17:22:49 +02:00
Aurélien Sibiril
8f6ecd9f81 Wire post-OAuth pickers and 2-auto callback handling → Track e2e gap for new access-review connectors
- 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>
2026-05-17 17:22:48 +02:00
Aurélien Sibiril
0147acd9f0 Add PKCE, token-body extras, AuthURL templating to OAuth2 → Add settings structs for Pattern-2 connector providers
- Add PKCE, token-body extras, AuthURL templating to OAuth2
- Add 13 connector provider enum values
- Add scopes, display names, name resolvers for 13 providers
- Add settings structs for Pattern-2 connector providers

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-17 17:22:48 +02:00
Sacha Al Himdani
eecbe4c46c Rename vendors to third parties
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>
2026-05-13 21:21:39 +02:00
Sacha Al Himdani
9eed0d71c8 Log identity_id on every authenticated request
Add a single info log line in each authn middleware once an identity is
resolved, so every authenticated request emits a record that ties the
request back to its user and credential:

- Cookie session middleware logs "session authenticated" with
  identity_id and session_id.
- API key middleware logs "api key authenticated" with identity_id and
  api_key_id.
- OAuth2 access token middleware logs "access token authenticated"
  with identity_id and access_token_id.

The credential IDs are row identifiers (GIDs), not the secret token
values, so they're safe to log and let operators correlate a request
back to the specific credential used. The log lines use the
request-scoped logger from httpserver.LoggerFromContext so they
inherit http_request_id and any other middleware-attached attributes.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-13 15:49:52 +02: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
Sacha Al Himdani
a67f2433b1 Address code review findings in relay fetch and iam
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-12 18:42:54 +02:00
Sacha Al Himdani
fc24306cd8 Address code review findings across relay, UI, and Go backend
- 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>
2026-05-12 18:07:34 +02:00
É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é
4a405ce16c Self-host common third party logos via S3
Fetch favicons at import time instead of calling Google's favicon
service per page load. Logos are stored as public files in S3 and
served through the existing /api/files/v1/{id} endpoint.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-05-12 16:43:36 +04:00