Commit Graph

2452 Commits

Author SHA1 Message Date
Sacha Al Himdani
86c45875a4 Whitelist ownership grants via allow policies
Replace the deny-based restriction on granting OWNER with role-scoped
allow policies so authorization fails closed: admins may create and
update memberships only when the assigned role is not OWNER, and the
absence of a target role no longer implies permission.

To keep console UI gating accurate without loosening the base grants,
the permission field gains an optional typed options argument
(PermissionOptionsInput) that forwards target_role into the dry-run
authorization. Only the two role-related console calls (create user,
update membership) pass it; the OWNER option stays hidden for admins via
the existing assignable-roles helper.

Add a non-regression test that an admin cannot promote a member to OWNER
while still being able to change members between non-owner roles.
2026-07-08 18:41:13 +02:00
Sacha Al Himdani
ff9cb881e8 Enforce owner-only member removal and ownership grants via policy
An organization ADMIN could hard-remove members, including OWNERs, because
removeUser (connect and MCP) only checked the weaker iam:membership-profile:delete
gate. Authorize the owner-only iam:membership:delete instead, and expose the
source attribute on MembershipProfile so the owner grant's non-SCIM condition
can match.

Consolidate ownership-grant authorization into policy for both createUser and
updateMembership: each resolver passes the requested role as a target_role
attribute and ADMIN is denied granting ownership via deny-create-owner /
deny-promote-owner. target_role is distinct from resource.role, which is the
target's current role and guards editing existing owners. With no callers left,
the iam:membership-role:set-owner action (grant and OAuth2 scope) is removed.

Also pass the authorized scope through to the RemoveUser/CreateUser services,
gate the console Remove action on iam:membership:delete, and add regression
tests plus a changelog entry.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-08 17:59:45 +02:00
Sacha Al Himdani
98f08b7439 Guard vetting agent HTTP tools against SSRF
The third-party vetting agent runs a suite of HTTP "security" tools on
the internal worker network against a caller-supplied URL that is only
validated for length and charset, not host. Several tools reached
internal, loopback, and link-local addresses:

  - analyze_csp used a bare http.Client with no host validation, no
    redirect control, and no rebinding-safe transport, reflecting the
    target's CSP header back to the caller.
  - check_security_headers, fetch_robots_txt, and fetch_sitemap
    validated only the initial host, then followed 3xx redirects with an
    ordinary client, yielding full-read SSRF via a redirect to an
    internal address.
  - check_cors validated the URL but still dialed through an ordinary
    transport, leaving it exposed to DNS-rebinding TOCTOU.

Route every one of these clients through the house-standard
httpclient.DefaultPooledClient(WithSSRFProtection()), which rejects
dials to loopback, private, CGNAT, link-local, ULA, IPv4-mapped, and
reserved ranges on the resolved peer IP at connect time (defeating DNS
rebinding on every redirect hop) and refuses cross-origin redirects.
download_pdf moves onto the same client, and the now-unused local
netcheck.NewPinnedTransport is removed. analyze_csp also gains an
up-front ValidatePublicURL check for a clean early error and scheme
enforcement.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-08 11:23:00 +02:00
Sacha Al Himdani
f83b42d2ec Rename signature filter state field to profileState
The DocumentVersionSignatureFilter exposed a field named `state` that
actually filters on the signatory's profile state, which was ambiguous
next to the signature `states` field. Rename it to `profileState`
(GraphQL) / `profile_state` (MCP) across the schema, spec, resolvers,
console app, and n8n node for clarity.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-07 16:06:56 +02:00
Cursor Agent
73023fc2b8 Fix Anthropic thinking budgets
Normalize Anthropic thinking budgets while building message parameters so budget_tokens stays below max_tokens with response headroom. When the configured max token budget is too small to support Anthropic's minimum thinking budget, omit thinking for that request instead of sending an invalid payload.

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

Co-authored-by: Sacha Al Himdani <SachaProbo@users.noreply.github.com>
2026-07-07 13:51:11 +00:00
Sacha Al Himdani
180a6a0420 Confine trust center reads and signatures to the page's tenant
The public trust API derived its authorization scope from client-supplied
global IDs, so a visitor on one trust center could resolve nodes, export
audit-report PDFs, and read or mutate electronic signatures belonging to
another organization (cross-tenant access).

Every trust API resolver now derives its scope from the active compliance
page's organization via compliancepage.ScopeFromContext, so reads are always
confined to the page's tenant. Cross-tenant or unknown IDs surface as
not-found instead of leaking data or returning a 500. Active/presence is
enforced upstream by the id and presence middlewares.

esign's signature operations (GetSignatureByID, AcceptSignature, RecordEvent)
now take a caller-provided scope instead of deriving one from the requested
ID, so signature reads and mutations are tenant-scoped at the source. This
removes the need for a resolver-level authorization helper; RecordEvent also
verifies signature ownership within scope before recording, since the event
foreign key is not tenant-composite.

Adds e2e non-regression tests covering owning vs. foreign trust center report
export and the generic node(id:) resolver.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-06 18:59:41 +02:00
Sacha Al Himdani
bbb3a24286 Exclude unpublished docs from available accesses
The available document access list backing Grant All included documents
whose trust center visibility is PRIVATE but that have no published
version. Require a published major so the grantable set matches the
request-all filter and the Slack notification, which already skip drafts
and hidden documents.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-06 18:48:09 +02:00
Sacha Al Himdani
cdd3af635b Hide unpublished and hidden docs in Slack alert
The trust center access-request Slack notification listed every document
referenced by a document access, including drafts with no published
version and documents whose trust center visibility is NONE. Skip those
so the message only surfaces documents a requester could actually be
granted.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-06 18:41:51 +02:00
Ludovic Vielle
1116fc6bb4 Use clientip.Extract for esign and session IP capture
Several HTTP entry points still parsed RemoteAddr directly, so behind
a layer-7 proxy they recorded the load balancer IP instead of the
signer's. Route NDA acceptance, signing events, document sign/approve,
and session updates through clientip.Extract, which honors Forwarded
and X-Forwarded-For when trustedproxy allows them.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-06 16:28:55 +02:00
Sacha Al Himdani
2ffeb7f3e8 Require set-owner authorization to create OWNER membership
An organization ADMIN could mint an OWNER membership via createUser, which
only gated iam:membership-profile:create and bypassed the owner-only
iam:membership-role:set-owner check that updateMembership already enforces.

Gate the requested role in both createUser entry points (connect resolver
and the MCP CreateUserTool) with an additional set-owner authorization when
the role is OWNER, mirroring updateMembership. Add a regression test that
locks the ADMIN/OWNER privilege boundary the fix relies on.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-03 17:37:26 +02:00
Bryan Frimin
bea410fbb7 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 11:53:57 +02:00
Bryan Frimin
d357d9ded9 Ignore error when partial are ok
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:13:00 +02:00
Bryan Frimin
7054540e9c Fix bad id used to call authorize
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:12:59 +02:00
Bryan Frimin
504cdc8f96 Ignore error when partial are ok
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:12:59 +02:00
Bryan Frimin
771edcc4fd Harden LoadByIDs func
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:11:50 +02:00
Bryan Frimin
a65d1096d6 Add gid set
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:11:47 +02:00
Ludovic Vielle
f30e63edd0 Update llm registry gen file
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-02 18:58:06 +02:00
Bryan Frimin
21d098afb1 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:51 +02:00
Bryan Frimin
79ba6b480a Fix range issue
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:50 +02:00
Bryan Frimin
7495d1d5a0 Add range support
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:50 +02:00
Bryan Frimin
8bbbca0d6d Remove no required header fields
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:49 +02:00
Bryan Frimin
b1cc17ab26 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:49 +02:00
Bryan Frimin
3c7a27b7ff Use stable url for public file
This will allow the CDN infrastructure to cache it properly

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-02 18:45:49 +02:00
Ludovic Vielle
be33f72f7d Point prb auth login at eu.probo.com and us.probo.com
The CLI still defaulted to *.console.getprobo.com for region
selection, examples, and the non-interactive fallback host. Align
those with the current SaaS hostnames so new logins store the right
config keys.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-02 18:25:41 +02:00
dependabot[bot]
a42df02829 Bump the go-dependencies group with 21 updates
Bumps the go-dependencies group with 21 updates:

| Package | From | To |
| --- | --- | --- |
| codeberg.org/miekg/dns | `0.6.81` | `0.6.83` |
| [github.com/99designs/gqlgen](https://github.com/99designs/gqlgen) | `0.17.90` | `0.17.93` |
| [github.com/anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go) | `1.48.0` | `1.55.0` |
| [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.42.0` | `1.42.1` |
| [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) | `1.32.23` | `1.32.27` |
| [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) | `1.19.22` | `1.19.26` |
| [github.com/aws/aws-sdk-go-v2/service/bedrockruntime](https://github.com/aws/aws-sdk-go-v2) | `1.53.4` | `1.54.2` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.103.2` | `1.104.2` |
| [github.com/aws/aws-sdk-go-v2/service/secretsmanager](https://github.com/aws/aws-sdk-go-v2) | `1.42.3` | `1.42.5` |
| [github.com/aws/aws-sdk-go-v2/service/ssm](https://github.com/aws/aws-sdk-go-v2) | `1.69.3` | `1.69.5` |
| [github.com/pdfcpu/pdfcpu](https://github.com/pdfcpu/pdfcpu) | `0.12.1` | `0.13.0` |
| [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) | `0.12.0` | `0.13.0` |
| [github.com/sigstore/sigstore-go](https://github.com/sigstore/sigstore-go) | `1.2.0` | `1.2.1` |
| [github.com/vektah/gqlparser/v2](https://github.com/vektah/gqlparser) | `2.5.33` | `2.5.35` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.52.0` | `0.53.0` |
| [google.golang.org/api](https://github.com/googleapis/google-api-go-client) | `0.283.0` | `0.287.0` |
| gopkg.in/dnaeon/go-vcr.v4 | `4.0.6` | `4.0.7` |
| [github.com/aws/smithy-go](https://github.com/aws/smithy-go) | `1.27.1` | `1.27.3` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.36.0` | `0.37.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.55.0` | `0.56.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.43.0` | `0.44.0` |

Updates `codeberg.org/miekg/dns` from 0.6.81 to 0.6.83

Updates `github.com/99designs/gqlgen` from 0.17.90 to 0.17.93
- [Release notes](https://github.com/99designs/gqlgen/releases)
- [Changelog](https://github.com/99designs/gqlgen/blob/master/CHANGELOG.md)
- [Commits](https://github.com/99designs/gqlgen/compare/v0.17.90...v0.17.93)

Updates `github.com/anthropics/anthropic-sdk-go` from 1.48.0 to 1.55.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-go/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-go/compare/v1.48.0...v1.55.0)

Updates `github.com/aws/aws-sdk-go-v2` from 1.42.0 to 1.42.1
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.42.0...v1.42.1)

Updates `github.com/aws/aws-sdk-go-v2/config` from 1.32.23 to 1.32.27
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.32.23...config/v1.32.27)

Updates `github.com/aws/aws-sdk-go-v2/credentials` from 1.19.22 to 1.19.26
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.19.22...credentials/v1.19.26)

Updates `github.com/aws/aws-sdk-go-v2/service/bedrockruntime` from 1.53.4 to 1.54.2
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/kms/v1.53.4...service/s3/v1.54.2)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.103.2 to 1.104.2
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.103.2...service/s3/v1.104.2)

Updates `github.com/aws/aws-sdk-go-v2/service/secretsmanager` from 1.42.3 to 1.42.5
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/amp/v1.42.3...service/amp/v1.42.5)

Updates `github.com/aws/aws-sdk-go-v2/service/ssm` from 1.69.3 to 1.69.5
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ecs/v1.69.3...service/ecs/v1.69.5)

Updates `github.com/pdfcpu/pdfcpu` from 0.12.1 to 0.13.0
- [Release notes](https://github.com/pdfcpu/pdfcpu/releases)
- [Commits](https://github.com/pdfcpu/pdfcpu/compare/v0.12.1...v0.13.0)

Updates `github.com/pires/go-proxyproto` from 0.12.0 to 0.13.0
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.12.0...v0.13.0)

Updates `github.com/sigstore/sigstore-go` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/sigstore/sigstore-go/releases)
- [Commits](https://github.com/sigstore/sigstore-go/compare/v1.2.0...v1.2.1)

Updates `github.com/vektah/gqlparser/v2` from 2.5.33 to 2.5.35
- [Release notes](https://github.com/vektah/gqlparser/releases)
- [Commits](https://github.com/vektah/gqlparser/compare/v2.5.33...v2.5.35)

Updates `golang.org/x/crypto` from 0.52.0 to 0.53.0
- [Commits](https://github.com/golang/crypto/compare/v0.52.0...v0.53.0)

Updates `google.golang.org/api` from 0.283.0 to 0.287.0
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.283.0...v0.287.0)

Updates `gopkg.in/dnaeon/go-vcr.v4` from 4.0.6 to 4.0.7

Updates `github.com/aws/smithy-go` from 1.27.1 to 1.27.3
- [Release notes](https://github.com/aws/smithy-go/releases)
- [Changelog](https://github.com/aws/smithy-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws/smithy-go/compare/v1.27.1...v1.27.3)

Updates `golang.org/x/mod` from 0.36.0 to 0.37.0
- [Commits](https://github.com/golang/mod/compare/v0.36.0...v0.37.0)

Updates `golang.org/x/net` from 0.55.0 to 0.56.0
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

Updates `golang.org/x/term` from 0.43.0 to 0.44.0
- [Commits](https://github.com/golang/term/compare/v0.43.0...v0.44.0)

---
updated-dependencies:
- dependency-name: codeberg.org/miekg/dns
  dependency-version: 0.6.83
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/99designs/gqlgen
  dependency-version: 0.17.93
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/anthropics/anthropic-sdk-go
  dependency-version: 1.55.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-version: 1.42.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.32.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-version: 1.19.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/service/bedrockruntime
  dependency-version: 1.54.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.104.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/service/secretsmanager
  dependency-version: 1.42.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/service/ssm
  dependency-version: 1.69.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/pdfcpu/pdfcpu
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: github.com/sigstore/sigstore-go
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/vektah/gqlparser/v2
  dependency-version: 2.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: golang.org/x/crypto
  dependency-version: 0.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: google.golang.org/api
  dependency-version: 0.287.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: gopkg.in/dnaeon/go-vcr.v4
  dependency-version: 4.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: github.com/aws/smithy-go
  dependency-version: 1.27.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-dependencies
- dependency-name: golang.org/x/mod
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: golang.org/x/net
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
- dependency-name: golang.org/x/term
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 18:05:43 +02:00
Cursor Agent
a0329a9696 Raise compliance report upload limit to 30MB
Third-party compliance report PDFs were rejected above 10MB in the
upload dialog. Increase the client-side Dropzone limit and enforce the
same cap on the backend file validator.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-02 16:20:51 +02:00
Sacha Al Himdani
1df4af4556 Add document webhook events
Add a resource-oriented set of webhook events for the document
lifecycle. Each event carries the document plus only the sub-resource
it concerns (version, signature or approval).

Events:
- document.created / updated / archived / unarchived / deleted
- document.version.created / updated / published / rejected / deleted
- document.version.signature.requested / signed / cancelled
- document.version.approval.requested / approved / rejected / voided

Wires the new types through the migration, Go enum, GraphQL schema,
CLI, n8n nodes and the console webhooks settings UI.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-01 17:13:59 +02:00
Sacha Al Himdani
7142757dd8 Add n8n trigger node for Probo webhooks
The n8n package could already manage webhook subscriptions through API
CRUD, but had no way to start a workflow when Probo emitted an event. A
user had to drop in the generic Webhook node, create a subscription by
hand, and verify the HMAC signature themselves.

Add a ProboTrigger node that owns the subscription lifecycle: it creates
the subscription on activation pointing at n8n's generated webhook URL,
re-checks and re-registers it if the URL drifts, and deletes it on
deactivation. The webhook handler recomputes the HMAC-SHA256 over the raw
request body and compares it constant-time against the delivered
signature, failing closed when the bytes or headers are absent.

Drop the MEETING_* event choices from the webhook create and update
operations and the CLI event list. They are not part of the backend
WebhookEventType enum, so selecting them only produced API rejections.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-01 16:06:08 +02:00
Ludovic Vielle
e22aaa8b67 Omit empty fields from bootstrap config output
probod-bootstrap was writing empty strings and stub blocks such as
`esign: {}` into generated YAML. The post-marshal prune pass caused
part of that by stripping empty leaf strings while leaving empty
parent maps behind.

Drop the prune round-trip in WriteConfig and rely on struct-level
omitzero/omitempty tags plus custom IsZero() helpers on probodconfig.
Only include LLM providers when an API key is set, use a nil map for
extra API headers, and extend the dev-config Makefile recipe with the
local dev defaults already documented in .env.example.

Config loading is unchanged: omitted keys still decode to Go zero
values.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-01 11:47:43 +02:00
Ludovic Vielle
35ea262691 Drop string defaults duplicated in probod.New()
probod-bootstrap was repeating probod.New() literals for string
env vars. Map those fields with getEnv only and leave int and bool
defaults in the builder. Runtime and generated yaml pick up probod
defaults when a variable is unset; dev and Helm still set env vars
explicitly where needed.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-30 23:49:54 +02:00
Bryan Frimin
3f70047f72 Allow json output
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-30 19:07:03 +02:00
Bryan Frimin
c3bb19a780 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-30 19:00:36 +02:00
Bryan Frimin
4e81061a1b Remove empty string value when generate cfg file
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-30 18:48:27 +02:00
Bryan Frimin
5b92a7ba5a Allow ephemeral ports for loopback redirect URIs
Native OAuth clients such as Claude Code publish loopback redirect URIs
without a port (http://localhost/callback) and pick an ephemeral port at
request time, as described in RFC 8252 section 7.3. The authorize flow
matched the requested redirect URI against the registered set with an
exact string comparison, so http://localhost:3118/callback was rejected
with invalid_redirect_uri even for a trusted, allow-listed client.

Make OAuth2Client.IsRedirectURIAllowed the single source of truth for
redirect matching: it keeps exact matching and adds loopback-aware
matching that ignores the port when scheme, host, path, and query agree.
The redundant document-level check and its duplicate loopback helper in
the CIMD resolver are removed, so both the registered-client and CIMD
paths now rely on one matcher.

Also add a pkg/netx package for the loopback helper.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-30 18:36:47 +02:00
Sacha Al Himdani
b77ced364b Write longer third-party risk assessment vetting notes
Expand buildRiskAssessmentNotes to persist most of the extracted
assessment (classification, per-category risk breakdown, privacy and
data processing practices, AI governance, contractual clauses,
professional standing, and baseline failures) instead of only a short
summary. Fields already stored as structured columns on the third party
(certifications, data locations, document URLs) are omitted to avoid
duplication.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-30 14:44:07 +02:00
Ludovic Vielle
888fa4d63a Return conflict when removing a referenced person
Deleting a profile still referenced elsewhere (for example as an
asset owner) surfaced an internal error. PostgreSQL reports ON DELETE
RESTRICT blocks as SQLSTATE 23001, not 23503; map both in profile
delete and propagate ErrProfileInUse through removeUser as CONFLICT.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-30 14:11:46 +02:00
Cursor Agent
79285d97df Remove access review framework controls
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-30 14:11:09 +02:00
Cursor Agent
9822b39ece Cast access review entry filters
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-30 14:11:09 +02:00
Cursor Agent
3880a09cea Use filtered entry count for campaign close
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-30 14:11:09 +02:00
Cursor Agent
9c09562918 Remove pending entry count field
Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-06-30 14:11:09 +02:00
Bryan Frimin
bf255b198c Bound GraphQL request cost to prevent alias-flooding DoS
The GraphQL endpoint built its gqlgen server with bare handler.New and
no limits, so a single request with thousands of aliased resolver calls
was parsed, validated, executed, and marshalled in full. Under load this
let an unauthenticated client drive excessive CPU and memory use against
POST /api/connect/v1/graphql and the console and trust endpoints, which
share the same constructor (GHSA-prh2-g8pv-m7p9).

Add configurable guards in the shared gqlutils.NewHandler: a parser
token limit rejects oversized queries at lex time before any execution,
a fixed complexity limit caps field-selection count, an LRU query cache
avoids repeated parsing, and field suggestions are disabled. The limits
flow from a new APIConfig.GraphQL section through server and api config
into all three GraphQL handlers, with PROBOD_API_GRAPHQL_* env vars and
Helm values exposed for per-environment tuning.

Defaults are sized with generous headroom over real traffic: the parser
token limit (15000) and complexity limit (2000) sit far above the
largest legitimate frontend query yet well below the proof-of-concept
flood, so normal usage is unaffected while floods are rejected cheaply.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-30 14:10:41 +02:00
Sacha Al Himdani
f7e93cdec5 Migrate Slack sender to kit worker
Replace the custom polling loop in the Slack sender with the
go.gearno.de/kit/worker framework, matching the email sending worker.
Add a processing_started_at column to slack_messages so claims are safe
under the worker's claim/process model, with stale recovery for crashed
in-flight sends. Rename sender.go to worker.go to match the kit worker
file naming convention.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-29 14:40:24 +02:00
Ludovic Vielle
5cff72ce5b Add Parameter Store to bootstrap resolver
probod-bootstrap could only resolve aws:// Secrets Manager refs.
Add awsps:// for SSM Parameter Store (GetParameter with decryption)
and awssm:// as an explicit Secrets Manager prefix. Keep aws:// for
backward compatibility. Values are cached per run per backend.
Update Helm, .env.example, and probod-bootstrap changelog.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-29 14:39:13 +02:00
Sacha Al Himdani
1948142e7a Run webhook sender on the kit worker framework
Convert the hand-rolled webhook Sender loop into a kit worker.Handler so
the sender gets the standard worker recovery, metrics and tracing
instead. Claim loads the next unprocessed webhook data and Process
performs the deliveries; behaviour is preserved with a single-concurrency
worker on the existing poll interval.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-26 16:27:34 +02:00
Ludovic Vielle
2ba8464d4e Fix advertised scopes for oauth protected resources
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-25 10:23:30 +02:00
Aurélien Sibiril
a0d3806c21 Add four API-key access-review connectors
Add Pylon, OpenRouter, incident.io and Brevo 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.

- Pylon: Bearer token, GET /users; resolves each user's opaque role_id to
  a role name via GET /user-roles, with cursor pagination.
- OpenRouter: Bearer management key, GET /api/v1/organization/members. The
  endpoint requires an organization account -- a personal key authenticates
  but returns 404 -- so the connection probe rejects 404 on top of 401/403
  (doProbeRequest gained an opt-in extra-reject set) to surface a non-org
  key at connect time instead of mid-campaign.
- incident.io: Bearer token, GET /v2/users. Its OAuth is outbound-only, so
  the API key is the inbound path; live base_role/custom_roles take
  precedence over the deprecated role enum.
- Brevo: API key in the api-key header (Registration.APIKeyHeader), GET
  /v3/organization/invited/users. A live recording corrected the documented
  schema: is_owner is a JSON boolean (not a string) and an id field is
  present, so it is used as the stable ExternalID.

The OpenRouter and Brevo cassettes are anonymized live recordings; Pylon
and incident.io use hand-authored fixtures (no self-serve test tenant). The
shared three-valued active-status mapping is consolidated into
activeFromStatus in driver.go.

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-24 22:22:51 +02:00
Ludovic Vielle
2b8f6f5b3b Add Secrets Manager resolution to probod-bootstrap
Introduce a Resolver that owns env lookup and typed parsing for
probod-bootstrap. Env values prefixed with aws://<secret-id> are
fetched from AWS Secrets Manager (plaintext SecretString); each
secret ID is cached per run. Builder now takes a Resolver only.

Prefix every probod-bootstrap input with PROBOD_ so bootstrap config
does not collide with unrelated process environment (for example
AWS_* used by other tooling). Secrets Manager authentication uses
the standard AWS SDK default chain (AWS_REGION, IAM role, profile);
PROBOD_AWS_* vars configure S3 in the generated config only.

Update Helm deployment env names, GNUmakefile dev-config, Lima
provision, e2e testutil, compose.prod.yaml, and docs.

Deployments must rename bootstrap env vars to PROBOD_* (e.g.
AUTH_COOKIE_SECRET → PROBOD_AUTH_COOKIE_SECRET).

BREAKING CHANGE: all env vars are now prefixed by `PROBOD_`.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-06-24 20:24:53 +02:00
Sacha Al Himdani
612e9cbc22 Move pending signature requests on minor publish
When a new minor version is published, still-REQUESTED signature requests
on the previous version now move onto the newly published version, keeping
the same signature row so the notification schedule (count and last-notified
time) is preserved. SIGNED signatures are left untouched.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-06-24 19:43:29 +02:00
Aurélien Sibiril
baf9ca2fe9 Add DocuSign partner OAuth2 with PKCE and picker
DocuSign approved our partner integration, so the connector can now
complete a real OAuth2 authorization-code flow. The integration key
has PKCE enabled, so RequiresPKCE is set; the confidential grant still
authenticates the token exchange with Basic auth and replays the
verifier as the documented hardening layer.

A DocuSign user may have access to several accounts, so this replaces
the previous auto-default-account behavior with a Pattern-1 picker:
the user chooses the account after OAuth, the choice is stored on
DocuSignConnectorSettings, and the driver and name resolver resolve
the selected account's data-center base URI from /oauth/userinfo.

Other changes:
- Request the extended scope so the refresh token's 30-day window
  rolls on each use; without it the token hard-expires 30 days after
  consent and breaks the connection.
- Drop API-key support: DocuSign has no static API key, only OAuth.
- Return ("", nil) from the name resolver on terminal failures so the
  source-name worker does not retry a revoked token forever.
- Add a driver test and cassette; the test previously skipped in CI
  for lack of a cassette.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
2026-06-23 22:11:44 +02:00
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