Commit Graph

2403 Commits

Author SHA1 Message Date
Sacha Al Himdani
474907e15c Enrich common third parties from production data
Fill missing fields (DPA, ToS, SLA, status/security/trust pages,
subprocessor lists, certifications).

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-23 19:36:34 +02:00
Aurélien Sibiril
0de4216ce6 Fix Heroku connection probe Accept header
Heroku's connection-status probe used a static ProbeURL, which the
generic probe issues with `Accept: application/json`. Heroku negotiates
the API version through the Accept media type and returns 400 for an
unversioned request, which doProbeRequest reads as "connected" -- so the
probe never caught a revoked token (it only surfaced at the first
ListAccounts).

Probe via a probeHeroku closure that sends
`Accept: application/vnd.heroku+json; version=3` instead. Verified live:
a dead token returns 400 with application/json but 401 with the
versioned header, which doProbeRequest correctly maps to rejected.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-23 19:21:13 +02:00
Aurélien Sibiril
6a285a59b9 Add five API-key access-review connectors
Add Mercury, Apollo.io, Deepgram, ClickHouse Cloud, and Langfuse as
access-review connectors. All are API-key, single-tenant providers
(Pattern 3): the key identifies one tenant, so there is no OAuth flow,
picker UI, or bootstrap/helm configuration.

- Mercury: Bearer token, GET /api/v1/users, cursor pagination.
- Apollo.io: x-api-key header, GET /api/v1/users/search (teammates).
- Deepgram: Token scheme; lists members across every project and
  dedupes by member_id, unioning per-project scopes.
- ClickHouse Cloud: HTTP Basic (keyId:keySecret); discovers the org
  via GET /v1/organizations, then lists its members.
- Langfuse: HTTP Basic (publicKey:secretKey); a base-URL setting
  selects the regional cloud host or a self-hosted instance.

Each adds the enum value, migration, GraphQL binding, provider
Registration, a driver with a cassette-driven test, and a brand logo.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-23 19:21:13 +02:00
Aurélien Sibiril
bd6a470d6d Add user:pass Basic auth mode for API-key connectors
The API-key connection transport could present a key as a Bearer token,
an x-api-key header, a custom scheme (SSWS/Token), or HTTP Basic with an
empty password (Cursor). None of these can carry a real password, which
providers such as ClickHouse Cloud (keyId:keySecret) and Langfuse
(publicKey:secretKey) require.

Add a fourth mode, APIKeyBasicAuthUserPass, that base64-encodes the
stored "username:password" credential verbatim into Authorization: Basic.
SetBasicAuth cannot express this -- it re-appends a ":" and corrupts the
credential. The mode is wired generically through the registry and the
create-connector resolver and is mutually exclusive with the other
API-key auth modes.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-23 19:21:12 +02:00
Ludovic Vielle
897ca031e2 Fix e2e tests following oauth2 scopes
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-23 19:17:56 +02:00
Ludovic Vielle
20b9321de3 Add read actions to all unprefixed scopes
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-23 18:24:50 +02:00
Sacha Al Himdani
a8e8e3e0e7 Allow signature requests only on current published version
Requesting a signature only validated that the version was PUBLISHED, so a
signature could be requested on a superseded (older) published version. Reject
versions that are not the document's current published major/minor, and hide
the request button in the console for non-current versions.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-23 16:39:18 +02:00
Ludovic Vielle
e424563794 Add RFC 6750 WWW-Authenticate on OAuth bearer APIs
Introduce BearerChallengeMiddleware on MCP, Console and Connect GraphQL, Files, and OAuth2 userinfo. Call sites record challenge intent in context via NoteUnauthenticated, NoteInvalidToken, and NoteInsufficientScope; the middleware applies resource_metadata, invalid_token, and insufficient_scope on WriteHeader.

OAuth2 access token middleware flags rejected Bearer tokens for invalid_token challenges. Add Authorizer.ScopesForAction for the scope auth-param.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-22 19:23:41 +02:00
Bryan Frimin
f2b979e6e7 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 17:31:15 +02:00
Bryan Frimin
2717bd2831 Fix conflict
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:42:20 +02:00
Bryan Frimin
87f769a816 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:18 +02:00
Bryan Frimin
cb7fb8f5e9 Split trust node lookup into node and aliasedNode
The trust node query previously accepted a String and resolved both GIDs
and slugs through one field, which forced the frontend to lose the ID
type guarantee. Restore node(id: ID!) as a strict GID lookup and add a
dedicated aliasedNode(alias: String!) that parses a GID first and falls
back to slug resolution before delegating to Node.

Inline the former nodeByGID switch directly into Node and drop the helper
file. Point the trust DocumentPage query at aliasedNode so slug-or-ID URLs
keep working.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:17 +02:00
Bryan Frimin
bfd672e0fe Export and rename resourceAliasForStorageResource
Rename the resolver helper to ResourceAliasResolver so it reads as a
resolver rather than a storage-resource-specific accessor, and update
its three call sites in the trust center resolvers.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:17 +02:00
Bryan Frimin
6758a1cd50 Move alias CLI under a resource-alias command
Promote the trust-center alias subcommand to a top-level
resource-alias command so aliases can be managed for any resource
from the CLI.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:16 +02:00
Bryan Frimin
5141478083 Replace trust center alias MCP tools with resource alias
Rename the setTrustCenterAlias and removeTrustCenterAlias MCP tools to
setResourceAlias and removeResourceAlias, backed by the resourcealias
service.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:16 +02:00
Bryan Frimin
7f47b6efb7 Resolve trust API nodes through resource aliases
Switch the trust center API to resolve aliased nodes via the
resourcealias service instead of the trust-center-specific alias
resolvers.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:10 +02:00
Bryan Frimin
231f7d153e Replace trust center alias with resource alias in console API
Drop the setTrustCenterAlias and removeTrustCenterAlias mutations and
the alias field on Audit in favor of generic setResourceAlias and
removeResourceAlias mutations backed by the resourcealias service.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:10 +02:00
Bryan Frimin
9b0a5745a0 Add resourcealias application service
Introduce a standalone resourcealias package with its own service,
IAM policies, and OAuth2 scopes so alias management no longer lives
inside the trust center services. Remove the trust-center-specific
alias services from probo and trust, and wire the new service into
probod, the server, and the API layer.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:40:09 +02:00
Bryan Frimin
2b8f0618de Generalize alias data layer to ResourceAlias
The trust_center_aliases table only allowed aliasing trust center
resources. Rename it to resource_aliases and scope uniqueness by
tenant rather than organization so any resource can carry an alias.
Replace the TrustCenterAlias coredata type with a ResourceAlias type
that loads and upserts aliases by resource ID.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:51 +02:00
Bryan Frimin
92b1264603 Fix alias resolver, field blur, and sitemap URLs
The audit alias resolver returned raw service errors. Log them
and return gqlutils.Internal like other resolvers in the file.

Remove-only users could edit the alias field to a new value that
was never saved. Reset local state when set permission is missing,
and catch mutation rejections on blur.

Sitemap generation appended audit report file IDs without
deduplication, which could emit duplicate document URLs when
multiple audits share the same report file.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:51 +02:00
Bryan Frimin
e2219c9d1a Normalize required fields in agent JSON schemas for OpenAI
OpenAI rejects schemas where optional properties are absent from the
required array. Promote all properties to required and mark formerly
optional ones nullable so the model knows it may pass null.

Also upgrade tool error log level from Warn to Error.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:50 +02:00
Bryan Frimin
982509416e Add trust-center alias CLI subcommand
Provides set and remove subcommands for managing trust center aliases
from the command line.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:50 +02:00
Bryan Frimin
a11fdc9520 Add setTrustCenterAlias and removeTrustCenterAlias MCP tools
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:49 +02:00
Bryan Frimin
02153ca0dc Add alias fields and alias-based node resolution in trust API
Node lookup now accepts an alias slug in addition to a GID, resolving
it against the organization's alias table before dispatching. Adds
alias fields to Document, AuditReport, and TrustCenterFile.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:49 +02:00
Bryan Frimin
c50aa28364 Expose alias field and set/remove mutations in console API
Adds an alias field to Document, Audit, and TrustCenterFile types.
Introduces setTrustCenterAlias and removeTrustCenterAlias mutations
with proper authorization and error handling.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:49 +02:00
Bryan Frimin
325465ab41 Add trust center alias service and integrate into sitemap
The sitemap now covers files and audit reports alongside documents
and resolves aliases so human-readable paths appear when configured.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:48 +02:00
Bryan Frimin
dbf022f772 Add TrustCenterAlias application service
Exposes Create, Remove, GetByResourceID, and LoadByResourceIDs.
Registers ActionTrustCenterAliasSet and ActionTrustCenterAliasRemove
under the trust center write OAuth2 scope.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:48 +02:00
Bryan Frimin
95a5227fcb Add TrustCenterAlias coredata layer
Supports upsert, load by alias, load by resource ID, bulk load by
resource IDs, and delete operations.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:48 +02:00
Bryan Frimin
ac5ee73997 Add trust_center_aliases table migration
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-22 11:38:48 +02:00
Ludovic Vielle
c93932f026 Introduce oauth2scope registry with freeze lifecycle
Replace pkg/iam/scopeset with pkg/iam/oauth2scope.Registry, a shared
OAuth2 scope→action registry used by the authorizer, OAuth2 service,
and Connect API. Registration stays open until probod calls Freeze();
read paths (RegisteredScopes, Allows, ValidateScopes) panic before
that.

Drop the leaky APIScopes surface and AllowedAPIScopes on manual
access-token creation in favor of registry.ValidateScopes. Metadata,
protected-resource metadata, and CIMD scope lists are built from
RegisteredScopes() via helpers in pkg/iam/oauth2/scopes.go. Expose
oauth2ScopesSupported as an OAuth2Scope GraphQL scalar.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-22 11:22:19 +02:00
Bryan Frimin
0d33750735 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-19 18:53:39 +02:00
Bryan Frimin
9fd95a0bf9 Fix missing cmid scope
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-19 18:49:33 +02:00
Bryan Frimin
d7e23fd890 Harden CIMD client resolution and caching
Tighten redirect URI validation for metadata documents, honor
Cache-Control no-store when caching fetched documents, and resolve
clients on the same transaction as authorization. Load
external_client_id from the database and parse unbounded max-stale
directives in cachecontrol.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-19 17:13:39 +02:00
Bryan Frimin
5b0d3e5052 Add OAuth2 Client ID Metadata Document support
MCP connectors such as ChatGPT and Claude register via HTTPS
client_id URLs instead of pre-provisioned GIDs. Fetch and cache
their metadata documents, upsert clients on first use, and
advertise CIMD in OIDC discovery when allowed URLs are configured.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-19 17:13:37 +02:00
Émile Ré
652be7a984 Stop sibling re-enqueue from flooding mapping logs
The tracker-mapping worker re-arms same-banner siblings after a pattern
resolves a vendor. Its predicate only excluded promoted siblings
(third_party_id IS NULL), but since org-party auto-creation was dropped a
pattern can resolve a common third party yet never gain an org
third_party_id. Those siblings, and terminal first-party ones, stayed
eligible forever, so every cascade step re-enqueued and reprocessed them,
amplifying Process runs to O(N^2) per banner. The deadlock fix in the
last release removed the rollbacks that had accidentally throttled the
cascade, so the latent amplification surfaced as an INFO-log flood.

Tighten the re-enqueue to skip siblings already linked to a catalog row
that carries a common third party or marked FIRST_PARTY, dropping
per-banner reprocessing back to O(N). Also demote the two per-run handler
logs ("mapped tracker pattern", "re-enqueued unmapped sibling tracker
patterns") to Debug so routine processing no longer logs at INFO.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 16:31:58 +02:00
Émile Ré
a4cc82441a Fix deadlock in concurrent tracker mapping
The tracker-mapping worker runs many Process calls in parallel. In
Phase 4 a single transaction locked the worker's own claimed pattern
row via UpdateMapping and then locked sibling rows on the same banner
via the re-enqueue. Two workers mapping sibling patterns on one banner
each held their own row and waited on the other's, forming a lock cycle
that Postgres aborted with deadlock detected (40P01).

Split the sibling re-enqueue into its own short transaction that runs
after the mapping commits, so the claimed-row lock is released before
any sibling row is locked. Also take the sibling UPDATE row locks in a
deterministic id order through an ORDER BY id ... FOR UPDATE subquery,
so overlapping re-enqueues can no longer invert lock order between
themselves. The re-enqueue only flags siblings, so deferring it past
the commit is safe and lets reprocessed siblings observe committed data.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 10:46:44 +02:00
Émile Ré
4435a93eca Rank HTTP cookie source above pre-existing
The tracker-pattern source ranking collapsed HTTP into the PRE_EXISTING
tier, so a cookie first enumerated as pre-existing and later re-observed
only via a Set-Cookie response header stayed pre-existing. That left it
on the agent-skipped tier (isPreExistingSource), even though an HTTP
server-set cookie is real page evidence, not the extension-state
catch-all the skip was built to suppress.

Give HTTP its own rank between SCRIPT and EXTENSION
(SCRIPT > HTTP > EXTENSION > PRE_EXISTING) in both sourceRank and the
bestSource merge rollup, so an HTTP re-detection now promotes the
pattern and re-arms mapping, unblocking the identification agent.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 10:46:44 +02:00
Émile Ré
a2d3852fea Give unlink full parity with first-party cleanup
Unlinking previously cleared only the catalog vendor link, leaving the
stale description that still named the removed vendor and leaving the
linked org tracker patterns pointing at it. Because the verdict becomes
UNDETERMINED the pipeline is meant to re-probe the row, so the stale
state was misleading until that happened.

Clear the description on both the catalog row and its uncategorised org
tracker patterns, and remap those org patterns so they drop the stale
vendor and re-resolve. A re-resolved vendor re-arms catalog enrichment
via the blank-and-unlinked upsert path, re-deriving the description.
This mirrors the cleanup mark-first-party already performs, minus the
terminal verdict.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 10:46:44 +02:00
Émile Ré
265c56d00b Improve tracker source and first-party cleanup
Surface every CookieSource value in the console: the trackers page
filter was missing the HTTP option and the source badge helper had no
EXTENSION case, so HTTP-sourced rows could not be filtered and
extension-sourced rows rendered the raw enum string.

On the backend, the mark-first-party verdict now blanks the stale
description on both the catalog row and its uncategorised org tracker
patterns. A terminal non-third-party row keeps no vendor link, so a
description naming the (now-cleared) vendor would be misleading; the
mapping worker only copies descriptions into empty rows and never
clears them, so clearing is done explicitly here.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 10:46:43 +02:00
Ludovic Vielle
6e6a093300 Add v1:iam:read scope to auditor mode
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-19 10:05:20 +02:00
Ludovic Vielle
0256babc9d Accept OAuth access tokens on MCP API
Manual OAuth bearer tokens worked on Console and Connect but
were rejected by MCP, which only ran the personal API key
middleware. Align MCP with the shared bearer chain used
elsewhere: API key, OAuth access token, then identity
presence. Drop the local RequireAPIKeyHandler.

Add e2e coverage for MCP calls authenticated with a manual
OAuth token, including scope enforcement.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-19 09:34:19 +02:00
Sacha Al Himdani
fdf5188707 Skip notifications for inactive users
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-19 08:59:54 +02:00
Ludovic Vielle
fd2e0903ee Register API scopes on prb CLI OAuth client
Device logins only requested OIDC scopes while the authorizer now
gates API calls on v1:* scopes. Register the full scope set on the
well-known prb client, request it at login via CLIClientScopes, and
cover the device flow in e2e.

Collapse API scopes under an accordion on the consent screen and
document scope sync for future namespace additions.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-18 20:11:55 +02:00
Ludovic Vielle
26c5002932 Add identity-scoped OAuth token management
Let users create, list, and revoke manual bearer tokens from
/me/oauth-tokens, scoped to their identity rather than an
organization. Manual tokens store a null client_id and are
authorized with a self-manage IAM policy.

Wire Connect GraphQL on Identity (list, create, revoke), add
console UI with scoped create flow and credentials dialog, and
cover the flow in e2e tests. Fix list pagination ordering and
keep the Relay connection in sync after create.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-18 20:08:49 +02:00
Ludovic Vielle
e20f1de58a Backfill OAuth2 access token API scopes
OAuth grant tokens issued before API scope enforcement only stored
OIDC scopes. Set every existing iam_oauth2_access_tokens row to the
full supported scope set so bearer tokens keep working under the
OAuth2 scope gate.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-18 19:07:28 +02:00
Ludovic Vielle
3ebb221a9b Add OAuth2 API scope registration and enforcement
Register v1 API scopes in coredata, advertise them in OIDC discovery
and protected-resource metadata, show them on the consent screen, and
enforce scope-to-action mapping in the IAM Authorizer before policy
evaluation.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-18 19:07:25 +02:00
Émile Ré
0247106581 Remove SQL comments from pattern upsert
The Upsert behavior is documented in the Go comment above the
method; inline SQL comments duplicated that and cluttered the
query.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-18 17:23:37 +02:00
Émile Ré
7de90d7915 Keep attribution consistent with vendor links
Three paths could leave a catalog row's attribution out of step with its
common_third_party_id. A FIRST_PARTY reclassification in the mapping
worker kept a stale org ThirdParty link instead of clearing it. The
upsert requeued terminal FIRST_PARTY rows for enrichment on a vendor
they never adopt, since the vendor-preservation clause nulls it. And the
proboctl upsert command did not normalize the verdict when an operator
linked or unlinked a vendor without passing --attribution.

Clear the org link on a first-party verdict, exclude FIRST_PARTY rows
from the enrichment requeue, and have the CLI downgrade THIRD_PARTY to
UNDETERMINED on unlink and promote UNDETERMINED to THIRD_PARTY on link.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-18 17:23:37 +02:00
Émile Ré
b64a33e868 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-06-18 17:23:37 +02:00
Émile Ré
973bd99a8f Add proboctl common tracker pattern upsert command
Mirror the common third party upsert for the tracker pattern catalog so
an operator can curate rows by hand. The command is keyed on the natural
key (tracker type, pattern, max age) rather than a slug, applies each
optional field only when its flag is passed, and reuses the existing
Upsert invariants that protect a terminal FIRST_PARTY verdict and never
clobber a researched description with an empty one. Pass --enrich to
re-arm the row for the async enrichment worker.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-18 17:23:37 +02:00