ListGoogleAnalyticsOrganizations decoded the response body into the success
struct before inspecting the HTTP status, unlike every other lister in the
file. Check the status first so a non-2xx no longer wastes a decode against
an error body and the ordering matches the sibling functions.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Segment's /users API exposes no active/suspended field, so reporting every
confirmed member as Active=true fabricated a status the source never
provides, contrary to the AccountRecord contract (nil = no explicit signal).
Leave Active nil for confirmed members; pending invites keep Active=false,
which is a real signal from /invites.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
listProperties filtered properties with parent:accounts/{id}, which returns
only properties whose direct parent is the account and silently drops
subproperties and roll-up properties (parented to another property). A member
holding a binding only on such a subproperty was omitted from the review.
Switch to the ancestor:accounts/{id} filter, which walks the whole account
hierarchy and is a strict superset, so no property is lost.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Cover the 2xx business/display-name path, the terminal non-2xx branches
(401/403/404/500 keep the generic source name), the Square-Version header,
and Google Analytics' empty-account-id short-circuit, matching the existing
resolver tests.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The shared VCR BeforeSave hook scrubs every other header-auth provider's key
(X-Api-Key, Api-Key, Signoz-Api-Key, X-Auth-Token) but was not updated for
Dotfile, so re-recording testdata/dotfile.yaml with a real X-DOTFILE-API-KEY
would persist the key into the committed cassette. Delete the canonicalized
X-Dotfile-Api-Key header alongside the others.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The GA4 driver passed the account and property IDs to url.JoinPath as raw
segments, and the Segment driver built its per-user endpoint by
concatenating the user ID into url.URL.Path — both bypass the url.PathEscape
rule that every sibling driver (and the matching name resolvers) already
follow. The IDs are numeric today so there is no behaviour change, but this
keeps the drivers consistent and safe if a provider ever returns a segment
with a reserved character.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Two OAuth2 and two API-key connectors:
- Google Analytics (GA4): OAuth2 with both analytics.readonly and
analytics.manage.users.readonly (readonly alone 403s on the accounts
list); v1alpha accessBindings enumerated at account and property level
and merged by email; manual account picker (Pattern 1) with a
per-connection probe and name resolver; distinct from Google Workspace.
- Dotfile: API key in the X-DOTFILE-API-KEY header (Pattern 3); GET
/v1/users (owner/admin, suspended_at) with a static probe.
- Segment (Twilio): Public API token as Bearer with a required Region
setting (US or EU) mapped to the regional host; GET /users plus per-user
GET /users/{id} for roles and /invites for pending members; per-connection
BuildProbeURL.
- Square: OAuth2 (EMPLOYEES_READ) or a personal access token (Pattern 3);
POST /v2/team-members/search returns email/status/is_owner directly, so no
role resolution; custom probe and name resolver.
Google Analytics and Square are confidential OAuth clients, wired into the
bootstrap OAuth provider list and .env.example. Segment carries a required
extra setting, so the console add-source dialog maps region onto its
segmentRegion API-key input; without that mapping the value is silently
dropped and the create is rejected.
Cassette-backed driver tests plus unit tests for the Segment probe URL and
the bootstrap OAuth provider list.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
All SAML assertion failures now share authentication_failed, so
the dedicated mapper and its tests are no longer needed.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
SAML refusals are shown as generic authentication_failed, so log
them at Error for investigation and drop the unused SAML error
code mapper.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Expected refusals already have a dedicated auth error page, so
warn logs just add noise. Keep Error logging for generic
authentication failures that still need investigation.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
TenantID is an [8]byte, so casting 1 does not compile. Use
NewTenantID like the other tests.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Specific SAML refusal reasons leak organization and account state.
Still redirect to the shared auth error page, but always use
authentication_failed while logging the real cause server-side.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Missing, expired, or reused OIDC state left users on a JSON error
or a generic failure. Map those cases to invalid_state so they get
a clear prompt to restart sign-in.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Browser auth callbacks still returned JSON or used one-off pages
for several refusal reasons. Route OIDC email verification,
magic-link, and SAML ACS failures through /auth/error with stable
error codes so users always see an explanation.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The check order is clear from the code and covered by tests.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
A dedicated personal-account page does not scale as more refusal
reasons appear. Redirect the OIDC callback to /auth/error with an
error code query parameter so the frontend can map codes to clear
user-facing messages.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Personal Google and Microsoft accounts were rejected with a raw
JSON unauthorized response after the OIDC callback. Redirect to a
dedicated auth page that explains the enterprise-account
requirement, and check enterprise eligibility before xms_edov so
Microsoft consumer accounts get the same clear error.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
go fix on Go 1.26 rewrites strings.Split range loops to
SplitSeq; apply that so lint-go passes.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
A single dnsExchangeTimeout around CheckCAA let slow empty
answers at child names consume the budget before parent
policy was queried. Give each label its own exchange timeout
inside the climb instead.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Keep the previous per-lookup dnsExchangeTimeout around CheckCAA
so rebased dnsclient calls do not drop main's deadline.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Extract shared DNS checks into dnsclient and fail closed on
truncated or non-success CAA responses. Climb past eTLD+1,
validate RFC 8659 issue-value syntax, and map NXDOMAIN TXT
lookups to the pending-verification path.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The check read only AutomaticCheckEnabled from /Library/Preferences,
falling back to softwareupdate --schedule when that key was absent.
Both describe automatic checking alone, so a Mac with downloads or
installs turned off still reported PASS. The key is also absent when
a configuration profile manages it, since the value then lives in
/Library/Managed Preferences, a layer never consulted.
Read the five Software Update preferences backing the System Settings
toggles, resolving each from the managed layer before the system one.
macOS treats them as enabled when unset, so only an explicit disabled
value fails. The now-unused softwareupdate binary leaves the command
allowlist.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
The macOS URL-handler bundle had no CFBundleIconFile, so
Finder showed the generic app icon after PKG install. Keep a
single master PNG and generate AppIcon.icns at build time
with sips/iconutil, matching the auditor-mode approach.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Custom-domain CNAME and SAML TXT checks accepted answers without
confirming the record owner matched the hostname being verified, so
an apex record could satisfy verification for a subdomain. CAA checks
also looked only at the exact hostname and ignored parent policy.
Add dnsverify helpers to compare owner names and walk from the
requested hostname up to the registrable apex for CAA (RFC 6844).
Require matching record owners for CNAME and TXT answers.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
When category was omitted from an MCP, GraphQL, or CLI update payload,
ThirdPartyService.Update overwrote the stored category with OTHER. Only
apply category when the request explicitly includes it, matching other
optional fields on the same update path.
Add an MCP e2e test that updates name without category and asserts the
existing category is unchanged.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Connect Organization has no email field. Requesting it made
the Get User GraphQL query fail; align with list/create/update.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Adds a @probo/skills skill for creating and updating the public
commitments (commitment groups and their commitments) shown on a Probo
compliance portal. Commitments are grounded strictly in the
organization's own published Probo policies and written in a factual,
understated engineering voice, following the get context -> draft ->
filter -> publish workflow via the Probo MCP.
- skills/compliance-portal-commitments/SKILL.md
- references/voice.md, references/portal-mechanics.md
- CHANGELOG.md entry under Unreleased
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Start-Process failures left $p null, then exit $null made
PowerShell return 0, so install/uninstall looked successful.
Make launch errors terminating and exit nonzero before
reading ExitCode.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
viewer.enrolledDevices shared DeviceConnection with the admin
org list, so totalCount had to authorize with both
employee-device:list and device:list. Mirror
EmployeeDocumentConnection: a dedicated EmployeeDeviceConnection
without totalCount, and keep DeviceConnection.totalCount for
the org fleet only. Cover assumed-session device:get IDOR in e2e.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Browser enrollment used osascript on every elevate. Ship a signed
privileged helper installed at PKG time so probo:// can enroll over
XPC with no second admin prompt. Add make install/uninstall/clean for
local PKG test loops, and show alerts only on failure.
Mirror the Go lint path for the macOS SPM package: Make
targets, root configs, and a Linux CI job. Keep checks
syntax-only so they do not need a macOS SDK. Format the
existing sources so the new gates start clean.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Browser enrollment succeeds once the device is ACTIVE, but the
macOS URL handler failed whenever install re-bootstrapped a tray
LaunchAgent the PKG had already installed. Skip registration when
the plist is current, treat live bootstrap as best-effort, and
exit successfully if the device is already enrolled so retries
do not show "Enrollment failed".
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
The /enroll wait UI polled device state via node(), which
requires an assumed org session, so confirmation never
succeeded for unassumed viewers. Expose viewer.enrolledDevice
behind itam:employee-device:get (own-device, skip assumption)
and point the poller at it.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Publish a notarized arm64+x86_64 .pkg from CI with the CGO tray
binary, Probo Agent.app, and global LaunchAgent. Keep the
LaunchDaemon enrollment-gated, align its plist path with the
launchd label, and document the Apple signing secrets.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Replace the placeholder orange-circle tray icon with the monochrome
Probo logo used in auditor-mode. On macOS, show the icon only (no
menu bar title); keep the title on Windows.
Restructure the context menu: status rows with colored indicators,
an Enroll via… submenu for region selection, and a clearer About
label. Surface enrollment errors in native dialogs instead of stderr.
Fix self-hosted enrollment on macOS by reading the hostname from
osascript's returned value; the default dialog output order broke
URL parsing and silently skipped opening the browser.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Concurrent enroll-url launches could both pass the enrollment
marker check and run overlapping elevated installs, racing on
LoadOrExchangeAPIKey and overwriting agent.key.
Add an exclusive flock on {configDir}/enrolling.lock for the
full install path and re-check IsEnrolled under that lock so
only one install exchanges a token and configures the device.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
The tray helper carried a ServerURL default that nothing read.
Unenrolled users can now open the console /enroll page from the
menu: US, EU, or self-hosted in production, or --server for dev.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Populate varied device posture states in seed data and link the
probo-agent release guide from AGENTS.md.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>