Commit Graph

2081 Commits

Author SHA1 Message Date
Cursor Agent
6f4ac54a27 Expose bridge sync errors in SCIM API
Add a dedicated SCIMBridge syncError field to the Connect GraphQL API
and populate it from the core bridge model.

Update the connector cards to read bridge state and syncError directly
from the bridge object instead of inferring failures from event
history.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 18:10:22 -07:00
Cursor Agent
4d4f4fa526 Expose profile source in MCP users
Add a ProfileSource schema to the MCP specification and include source\nin the Profile response schema so MCP clients can distinguish\nmanual and SCIM-managed users.\n\nWire the source field in the Profile mapper so list, get, and\ncreate user responses return the persisted profile source.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-28 00:25:50 +00:00
Cursor Agent
f50289a755 Harden archive organization scoping
Validate ArchiveUser organization input against the loaded profile and\nuse the profile organization for owner checks and webhook emission.\n\nAlso disable both PersonPage destructive actions while either archive\nor remove mutation is pending to prevent double-submit races.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:19 -07:00
Cursor Agent
7bb3c144a3 Fix wsl spacing for invitation filter
Add the required blank line before the pending-invitation filter assignment\nin ArchiveUser to satisfy wsl_v5.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:19 -07:00
Cursor Agent
b75c7880c0 Fix archive user lint spacing
Add the required whitespace before the invitation expiration if-block in\nArchiveUser to satisfy wsl_v5 lint rules.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:18 -07:00
Cursor Agent
41991f3879 Expire invitations when archiving user
Ensure ArchiveUser deactivates pending invitations for the archived\nprofile so invited accounts cannot still activate after archive.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:18 -07:00
Cursor Agent
0f8c9e1217 Pass scope into ArchiveUser
Change ArchiveUser to receive an explicit coredata scope from callers\ninstead of rebuilding scope from organization ID inside the service.\n\nWire connect and MCP resolvers to pass the authorized scope through to\nArchiveUser.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:18 -07:00
Cursor Agent
affed3c781 Remove extra delete error mapping
Drop the additional RemoveUser dependency-error handling that was added\noutside this PR scope.\n\nKeep the separate remove and archive operations, but let delete failures\npropagate through existing wrapped errors without the custom helper and\nreferenced-records error type.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:17 -07:00
Cursor Agent
1e08a23ddc Split user remove and archive actions
Restore RemoveUser as a hard delete operation and surface dependency\nconflicts with a dedicated IAM error.\n\nAdd a new ArchiveUser flow that deactivates profiles while keeping the\nmember in the organization, then expose both actions across Connect, MCP,\nCLI, n8n, console UI, and e2e coverage.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:17 -07:00
Cursor Agent
23070b18b5 Sync user archive across all interfaces
Align user-removal UX and API surface semantics with the new archive\nbehavior for manually managed users.\n\nFrontend copy and actions now use archive wording, and list rows are\nrefetched after the mutation so archived users reappear as inactive.\n\nMCP removeUser now documents and returns archived_user_id, n8n labels\nand response mapping now use archive semantics, and the CLI gains a\nuser archive command backed by the same mutation.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-27 12:23:16 -07:00
Cursor Agent
ebe9cc0e65 Fix wsl spacing in remove user flow
Add the required blank line before the profile-state conditional in\nOrganizationService.RemoveUser so the wsl_v5 lint rule passes in CI.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-27 12:23:16 -07:00
Cursor Agent
b50bbc8d6a Archive manual users on remove
Switch remove-user behavior for manually managed profiles from hard\ndelete to archival by deactivating the profile. This matches the\nrequested SCIM-like lifecycle while avoiding dependency errors for\nlinked records such as signatures and assets.\n\nThe remove flow now updates profile state to INACTIVE, updates\nmembership timestamps, and emits a user-updated webhook event instead of\ndelete events. E2E coverage now asserts that remove keeps the profile\nand marks it inactive.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
2026-05-27 12:23:07 -07:00
Cursor Agent
ffa3db3cd2 Return conflict when user has linked records
Map remove-user foreign key failures to a dedicated IAM conflict error so\nconsole users no longer receive a generic internal server error when a\nprofile is still referenced by signed documents, tasks, assets, or\nother records.\n\nThe service now detects wrapped dependency errors across both profile\nand membership deletes, and resolvers explicitly map the new error type\nto conflict responses. Added unit tests cover dependency detection and\nthe user-facing error message.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 12:23:05 -07:00
Cursor Agent
a7639be86b Handle archived HubSpot users
Parse HubSpot archived and deactivated status signals when building
access-review account records. This preserves inactive users as
explicit records instead of dropping them when status fields differ
from the current payload assumptions.

Also accept roleIds payloads and keep records that only expose an
external ID, which is common for archived users with empty email
fields. Add a focused unit test to lock in archived-user handling.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-05-27 19:10:37 +00:00
Sacha Al Himdani
29051e1f80 Grant SCIM bridge get/manage in IAM policies
The MCP getSCIMBridge tool authorized on iam:scim-bridge:get, but no
role policy allowed that action, so every call denied — including for
owners and admins.

Owners now get the full iam:scim-bridge:* wildcard (matching the
scim-configuration and scim-event treatment in the same policy).
Admins get read-only access and are explicitly denied create/update/
delete, mirroring how scim-configuration is handled.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-27 20:13:29 +02:00
Sacha Al Himdani
b30c476ffe Drop snapshot schema
Drops the now-unused snapshot_id/source_id columns from every table that
carried them, the snapshots and controls_snapshots tables, the
snapshots_type enum, and the snapshot-scoped indexes. Recreates the
unique indexes that previously gated on snapshot_id IS NULL without the
snapshot scope. The data was already cleaned up in the prior PR.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-27 18:40:55 +02:00
Sacha Al Himdani
e6f7884a29 Fix common third-party logo URL not-found error
CommonThirdParty.AuthorizationAttributes returned an empty map, but
the IAM authorizer requires an entry for every requested resource ID
and treats missing entries as ErrResourceNotFound. With the new
authorize() guard on commonThirdPartyResolver.LogoURL, this surfaced
as "commonThirdParties[*].logoUrl resource not found" from the
CommonThirdPartyComboboxQuery.

Switch AuthorizationAttributes to a SELECT-by-id existence check that
returns an empty attributes map for each row that exists. The catalog
is global with no organization, so omitting organization_id is the
intended shape: the authorizer then loads no membership, evaluates
only identity-scoped policies (CommonThirdPartyCatalogPolicy grants
access), and skips audit logging.

Add an e2e regression test that seeds a CommonThirdParty directly via
the test database and asserts the GraphQL logoUrl path resolves
without error.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-27 17:30:16 +02:00
Aurélien Sibiril
9b1034e361 Skip unconnectable providers in provider listing
ConnectorProviderInfos listed every known provider, including ones
this deployment cannot connect: no OAuth client credentials configured
and no API-key or client-credentials fallback supported. Those
rendered as dead entries the operator has no way to use, so skip them.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-27 00:34:39 +02:00
Aurélien Sibiril
a5259fc978 Refine connector provider registry per review
Three follow-ups from review of the registry consolidation:

- Build Vercel's authorization URL with net/url instead of a
  hand-rolled "{integration_slug}" placeholder resolved by
  strings.ReplaceAll. The slug is escaped via url.PathEscape in a
  per-provider Registration.BuildAuthURL closure, and the unused
  AuthURLParams plumbing on Registration and OAuth2Connector is
  removed (OAuth2Connector now carries a typed IntegrationSlug).

- Drop the SettingsInput union type and the per-provider
  MarshalSettings closures. The create resolvers now build the typed
  coredata.*ConnectorSettings directly from the gqlgen input, the
  same way the OAuth callback path already does, so there is no
  shared catch-all DTO and no stringly-typed boundary.

- Restore ConnectorProviders() to a plain ordered slice literal; the
  intermediate map + slices.Sort added nondeterminism and a sort for
  no benefit.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-27 00:34:39 +02:00
Aurélien Sibiril
e18ecdda8b Consolidate connector provider dispatch behind a typed *Registry
The console previously dispatched per-provider logic through a fan of
init()-side-effect maps (driver names, OAuth2 metadata, probe URLs,
display names, settings switches), spread across pkg/connector,
pkg/accessreview/drivers and the console v1 resolvers. Adding a new
provider required edits in every one of those places and a corresponding
switch arm in CreateConnectorRequest. The same per-provider knowledge
also leaked into Helm templates as hand-rolled environment-variable
blocks per connector.

This commit collapses the dispatch surface into a single typed
*provider.Registry. The registry is constructed once by
NewBuiltinRegistry at probod startup and threaded as an explicit
dependency into every consumer (accessreview service, console v1
resolver, OAuth2 wiring). There is no package-level state. Each
provider lives in one file under pkg/connector/provider/ that exposes
a private xxxRegistration() *Registration constructor; NewBuiltinRegistry
enumerates them.

CreateConnectorRequest loses its per-provider settings fields and
takes a single RawSettings json.RawMessage produced by the
per-provider MarshalSettings closure. The 1Password SCIM bridge URL
is validated at create time (http(s) scheme + non-empty host) so a
malformed value fails fast at the resolver boundary. The Helm chart
gains probo.connectorEnv and probo.connectorSecretEntries templates
so adding a connector requires zero Helm changes. Access-review name
resolution moves into the same Registration value to keep one
authoritative dispatch table.

Tests cover every Registration (DisplayName, NewDriver wired),
Register error paths (nil, empty Provider, empty DisplayName,
duplicate), All / ProviderDisplayName / ProviderOAuth2Scopes /
ProbeURL hit and miss paths, the ApplyOAuth2Defaults templating and
PKCE branches, and ConnectorSettings[T] round-trip plus malformed-JSON
error path. The pre-refactor ApplyProviderDefaults test in
pkg/connector is replaced by the equivalent in
pkg/connector/provider.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-27 00:34:39 +02:00
Bryan Frimin
f016e2cf06 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 15:26:22 -07:00
Aurélien Sibiril
0c34d4fa05 Fix SA5011 nil-deref lint in validator security test
CI's staticcheck does not treat t.Fatal as terminating, so every
`err.Message` deref sitting after a separate `if err == nil { t.Fatal(...) }`
block is flagged SA5011 (possible nil dereference). Collapse all 26
occurrences in TestNoHTML / TestPrintableText / etc. into a guarded
`} else if`, matching the fix already applied to validator_format_test.go
in bca36636f. The deref now lives in a branch where err is provably
non-nil, which satisfies SA5011 regardless of t.Fatal recognition.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-05-26 15:07:18 -07:00
Bryan Frimin
b8ea34cdea Fix test linux
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 15:02:50 -07:00
Bryan Frimin
bca36636f9 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 14:29:48 -07:00
Bryan Frimin
4296d168aa Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 14:18:34 -07:00
Bryan Frimin
138f25a170 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 14:11:59 -07:00
Bryan Frimin
51f37b9d85 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 14:07:22 -07:00
Bryan Frimin
712f00f434 Update copyright year
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:59:17 -07:00
Bryan Frimin
c46eab5c6e Add i3, kde and more support
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:55:43 -07:00
Bryan Frimin
5c73886faf Fix label name
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:47:14 -07:00
Bryan Frimin
7a3d3db7ac Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:58 -07:00
Bryan Frimin
e040851a4c Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:57 -07:00
Bryan Frimin
e6f988834d Apply go fix stringsseq rewrites to platform-specific check files
Replace strings.Split + for-range-slice with strings.SplitSeq +
for-range-iterator in checks_linux.go and checks_windows.go. These
files are invisible to go fix on macOS (darwin build tag), so the CI
Linux runner caught them first.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:57 -07:00
Bryan Frimin
941ab121da Reject shell-unsafe paths in FreeBSD rc.d install
Validate executable and state directory paths before rendering the
rc.d script so crafted values cannot inject shell syntax.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:57 -07:00
Bryan Frimin
27cccd33aa Make Windows service uninstall idempotent
Treat sc.exe delete error 1060 as success when the service is
already absent so repeated uninstalls do not fail.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:56 -07:00
Bryan Frimin
d310886560 Use platform-specific atomic key replacement on Windows
Swap the keystore through a temp and .old file on Windows instead
of relying on os.Rename alone, which is not reliably atomic there.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:56 -07:00
Bryan Frimin
0a4922f571 Share copyFile helper across Unix and Windows updaters
Move the duplicated file-copy logic into update/copy.go so both
platform installers stay in sync during maintenance.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:56 -07:00
Bryan Frimin
75051cc150 Handle FreeBSD check command failures before reading status
Return unknown when service status commands fail so remote login
and malware checks cannot pass on empty output after an error.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:55 -07:00
Bryan Frimin
0d3b526db0 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:55 -07:00
Bryan Frimin
22e50b3f11 Add probo-agent CLI and deviceagent library
Introduce the standalone device agent binary and shared library
for enrollment, posture checks, self-update, and OS service
integration. Include build targets, module deps, and release
workflow so the agent can ship independently of server changes.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 13:45:54 -07:00
Bryan Frimin
59f8e943fb Fix open redirect bypass in saferedirect
Relative redirect URLs are now normalized before validation. Paths
containing backslashes (including percent-encoded %5c) are rejected,
closing a bypass where /../\evil.com passed checks but http.Redirect
normalized to /\evil.com.

Reported by Fushuling and RacerZ.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-05-26 11:41:33 -07:00
Émile Ré
dc92fd238f Fold PromoteSource into Update
Every PromoteSource caller already loaded the tracker pattern under
the same transaction, so a dedicated single-column UPDATE only
duplicated machinery and forced callers to learn a second mutation
verb. Add `source = @source` to Update's SET clause, mutate
Source/UpdatedAt on the receiver, and call Update at the three
promotion sites (worker merge loop, worker adoption loop, and
reportDetectedTracker). The shouldPromoteSource gate still ranks the
candidate against the loaded value; Update is now the single write
path that can advance source, with a doc comment spelling out the
load-first contract.

Re-cast the coredata tests around Update: WritesSource pins the
round-trip from receiver to DB, NotFoundForMissingRow preserves the
ErrResourceNotFound contract callers rely on. The old
OnlyTouchesSourceAndUpdatedAt test was a property of the narrow
PromoteSource UPDATE and no longer applies — Update intentionally
rewrites the full editable column set from the receiver.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:55 +02:00
Émile Ré
27a9f83825 Promote on detection
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
Émile Ré
ceb98740a5 Fix lint
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
Émile Ré
05cbab7258 Promote glob source and trigger draft on adoption
The pattern-analysis worker dropped two signals on every run. When
InsertIfNotExists hit a pre-existing glob, the computed bestSource
was discarded by the LoadByBannerIDTypeAndPattern fallback, so the
SCRIPT > EXTENSION > PRE_EXISTING precedence advertised on
bestSource was only ever enforced at first insert. Subsequent
batches with stronger sources could not promote the glob, even
though the page-script-wins rule already lives in detected_trackers
at the row level.

Separately, adoptUncategorisedPatterns returned an adopted bool
that the worker discarded; the function moves detected trackers
from uncategorised exact patterns into categorised globs, which is
a real consent transition, but no draft banner version was created
on adoption-only runs.

Add a focused TrackerPattern.PromoteSource that only updates the
source and updated_at columns. Express the precedence as a pure-Go
shouldPromoteSource helper alongside bestSource so the rule is
unit-testable without a database. The worker now calls
InsertIfNotExists, then on conflict loads, skips when the slot is
held by an exact pattern or a user-recategorised glob, and only
calls PromoteSource when the candidate source ranks above the
existing one.

The skip branch is now documented: adoptUncategorisedPatterns is
the safety net that re-homes uncategorised exacts into the existing
glob via globMatch. Capture its adopted return value and use it
(instead of the previous over-eager consentChanged flag) to gate
ensureDraftVersionForBanner. Merging exacts into a glob in their
own category never changes visitor consent, so the prior flag
produced redundant draft versions on every non-uncategorised merge.

Cover the new pieces with three test layers: pure-unit cases for
shouldPromoteSource (precedence matrix including HTTP/nil collapse
and equal-rank no-write), DB-backed tests for PromoteSource (touch
only source + updated_at, ErrResourceNotFound for missing rows),
and end-to-end worker tests for source promotion on an existing
glob, draft-on-adoption, and the merge-only no-draft case.

Signed-off-by: Émile Ré <emile@probo.com>
2026-05-26 18:06:54 +02:00
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