Commit Graph

5019 Commits

Author SHA1 Message Date
Cursor Agent
f1995f2c8f Validate marketplace JSON structure in skills package
Parse and check Claude and Codex marketplace catalogs for
required fields, plugin sources, and npm package name alignment.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-10 13:37:25 +00:00
Cursor Agent
d7e82ca1f6 Fix review findings in @probo/skills package
Add ISC header to COMPATIBILITY.md, validate Claude marketplace
json, record access-review notes after API success, checksum npm
pack contents, trim dead package.json files entries, and mark
listUsers organization_id as required.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-10 13:29:26 +00:00
Cursor Agent
f864482ce1 Remove placeholder .gitkeep from skills package
Drop empty agents/ and hooks/ directory placeholders until
those folders have real content.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-10 13:06:04 +00:00
Cursor Agent
e3209b74ae Add @probo/skills multi-agent compliance package
Ship compliance skills and agent plugin wiring for Claude Code,
Codex, OpenCode, and Cursor via Probo MCP (OAuth 2.0). Includes
access-review and missing-signatures workflows, repo-root Git
marketplaces, validation, and npm release CI.

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

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
2026-07-10 12:59:49 +00:00
Émile Ré
a6aabc6dad Address updates pages review feedback
Forward className on PaginationSkeleton to match the kit skeleton API,
and move the Next arrow to iconEnd per the forward-arrow convention.

Size the updates list skeleton to the page size so the placeholder no
longer jumps when the loaded page renders, and dispose the detail
query on updateId change to avoid a flash of the previous update.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 19:39:16 -04:00
Émile Ré
5b2d1598de Add Storybook stories for Pagination
Cover the playground, the both/first/last arrow states that show the
reserved-slot positioning, the optional label, and the skeleton.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 19:15:41 -04:00
Émile Ré
886cb36f98 Refine updates pages styling and pagination
Generalize the cursor Prev/Next pagination hook into a reusable
useCursorPagination in lib/relay, taking the page size as a parameter,
and keep the updates page size (25) as a feature constant.

Move the list card surface and its loading-dim state into tv variants
behind an UpdatesList component, and lift the detail article layout and
its gold metadata styling into shared variants, so the pages carry only
placement classes. Skeletons reuse the same variants.

Relocate the generic pager labels to the app-root namespace and expose
Intl.DateTimeFormat options on the formatDate helper.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 18:02:16 -04:00
Émile Ré
a238f78768 Add trust center updates list and detail pages
Build the public Updates pages in the compliance portal: a
cursor-paginated list of sent mailing-list updates and a detail view
for a single update, replacing the previous stub page.

Add a MailingListUpdate case to the trust API node resolver, guarded so
only SENT updates belonging to the current trust center's mailing list
are exposed, so the detail page can load an update by URL.

Add a Prev/Next Pagination primitive to the v2 UI kit. Page numbers are
omitted because cursor pagination cannot derive an ordinal page index;
each arrow only shows when its page exists while keeping its slot
reserved so a visible arrow never shifts position.

Relocate the shared MailingListUpdateListItem to its own component
folder and wrap each row in a link to the detail page, so both the home
recent-updates section and the list navigate to detail.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 17:45:13 -04:00
Émile Ré
99fa2bdde8 Share trust center test lookup and activation helpers
Two files declared activateTrustCenter in package trust_test, so the
package no longer compiled, and the trust-center lookup plus activation
logic was duplicated four ways across the suite.

Move lookupTrustCenterID and activateTrustCenter into a shared
helpers_test.go and route every call site through them, dropping the
inline copies in the logo and report-export tests.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 10:05:05 -04:00
Émile Ré
fbbbfe9bf5 Stretch subprocessor cards to align bottom edges
Drop items-start from the category grid so its rows stretch again.
Each card is a block grid item that fills the row height, lining up
the bottom edges of cards on the same row regardless of how much text
each one carries.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:38 -04:00
Émile Ré
9f44e3a297 Fix subprocessor card vertical spacing
Drop the region icon top margin so it centers with its label line,
and stop the card grid from stretching rows. Cards now hug their
content, giving every card a consistent 32px bottom padding instead
of leftover space below shorter cards.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:38 -04:00
Émile Ré
c7c05f0e8d Harden subprocessor filters per review feedback
Address the review comments on the subprocessors work:

- Reject invalid category/country filter values in the Subprocessors
  resolver with an INVALID error instead of relying solely on transport
  coercion, so a malformed request fails fast and explicitly.
- Use pgx.StrictNamedArgs in the new distinct facet queries so missing
  or extra SQL placeholders stay detectable, matching sibling queries.
- Default a nil ThirdPartyFilter at the service boundary to avoid a nil
  dereference in the coredata list/count paths.
- Expose the category group label as an aria heading for assistive tech.
- Add the missing space in the Select "Selected:" story label.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:38 -04:00
Émile Ré
52b6ccac55 Populate subprocessor filters from server facets
The subprocessors toolbar derived its category and region filter
options client-side from a second, unfiltered subprocessors(first: 250)
fetch, shipping up to 250 rows purely to compute two small facet sets
and silently capping the options at that limit.

Expose subprocessorCategories and subprocessorCountries on TrustCenter,
each backed by a DISTINCT query over the organization's third parties
scoped to show_on_trust_center, and read them directly in the toolbar.
The page now issues one filtered list query plus two tiny arrays, and
the options only ever include values that can actually return results.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:38 -04:00
Émile Ré
1e946d065c Extract BackdropCard and EmptyState components
The subprocessor list item, commitment card, and empty state each
inlined layout and color Tailwind classes on className, against the v2
UI rules that keep look-and-layout in tailwind-variants slots.

Move the shared soft-Card frame (a backdrop header faded over a body)
into a presentational BackdropCard, and the icon/title/description
placeholder into a generic EmptyState, both under components/. Refactor
CommitmentCard and SubprocessorListItem onto BackdropCard, and
SubprocessorsEmpty onto EmptyState, leaving only component-specific
slots in each variants.ts.

Subprocessors without a website now show the dotted backdrop behind the
fallback icon, matching the commitment card.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
1933c914ac Add missing variants on TextField and Select
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
c6e1f8e3b1 Fix lint issues
Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
8ed92e13b3 Document the list-filtering pattern
Codify the lessons from the subprocessors filter fix as reusable guidance:
a pure URL-state filter hook (never a per-instance mirror + write-back
effect), a single-owner debounced search hook, and refetching inside a
transition to scope the loading state to the results instead of the
whole-page Suspense fallback.

Add a list-filtering Cursor rule and expand the state-management and relay
guides with the corresponding sections.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
b4b6599907 Fix subprocessor filter loop and scope refetch loading
Searching subprocessors flipped the list between filtered and unfiltered
results in an infinite loop: useSubprocessorFilters kept a local search
mirror plus a write-back effect, so every component calling it (page,
loader, toolbar, empty state) ran its own effect while only the toolbar
updated the mirror — the stale instances fought the real writer.

Make useSubprocessorFilters a pure URL-state hook and move the debounced
search input into a single-owner useSubprocessorSearch hook mounted only
by the toolbar, guarding the URL-to-input sync with a ref so its own
commits are not echoed back.

Refetch on filter change now runs inside a transition so the toolbar and
current results stay mounted instead of falling back to the whole-page
Suspense skeleton; only the results container dims while loading.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
c9b74d6de0 Filter trust center subprocessors server-side
Subprocessor filtering for the compliance portal happens in the backend
rather than the client. Add a SubprocessorFilter (query, category,
country) to the trust API's subprocessors connection, thread it through
the resolver and service, and extend the coredata ThirdParty filter with
category equality and country array membership. The connection stores the
filter so totalCount reflects the filtered set. Add e2e coverage for the
new filtering.

On the frontend, convert the page to a refetchable fragment whose filter
arguments are driven by URL-persisted, debounced toolbar state (category
and region selects plus a search field), populate the dropdowns from an
unfiltered facet selection, and offer to clear filters from the empty
state.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:37 -04:00
Émile Ré
f1f4c93104 Add compliance-portal Subprocessors page
Replace the Subprocessors placeholder with the real trust-center page from
the Figma design: subprocessor cards grouped by their backend category,
each showing a favicon logo over a blurred backdrop, the name, the
description, and the hosting regions. Country codes render through
Intl.DisplayNames and the section labels/descriptions come from a new
page-scoped i18n namespace.

Migrate the route to the per-resource folder layout (pages/subprocessors/
with its own routes.ts, loader, page, skeleton, _components, _lib, and
_locales) and drop the old flat stub. Filtering is added separately.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:36 -04:00
Émile Ré
502fb08054 Add v2 Select and TextField UI kit components
The compliance-portal Subprocessors toolbar needs a dropdown filter and a
search input, which the v2 kit did not provide. Add Select (styled over
Base UI's headless select) and TextField (over Base UI's input) following
the v2 conventions: Base UI primitives, tailwind-variants/lite slots,
flat folders, bundle-safe variants, and paired skeletons. Both map the
Figma semantic colors to the Radix sand/gold token scales.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-09 09:51:36 -04:00
Sacha Al Himdani
eb9274f2cd Document three published advisories in security notes
Add user-facing security notes for the three advisories published on
2026-07-09: the ADMIN-to-OWNER privilege escalation via createUser
(GHSA-cppp-g98f-gfpp), the unauthenticated cross-tenant and
hidden-item disclosure via Query.node in the Trust Center API
(GHSA-w23w-f7v2-625w), and the broken access control in the public
e-signature NDA API (GHSA-22xj-f767-ppw6). Each note summarizes the
flaw and the shipped remediation and credits the reporter.

Also backfill the advisory and CVE references on the existing open
redirect note, which already had a fix but no published identifiers.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-09 12:24:12 +02:00
Sacha Al Himdani
920cbb0a26 Release probod/v0.224.1
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-09 11:37:10 +02:00
Sacha Al Himdani
ed9cdb4847 Update Go to 1.26.5 to fix stdlib CVEs
Bump the Go toolchain from 1.26.4 to 1.26.5 to address CVE-2026-42505
(ECH handshake de-anonymization) and CVE-2026-39822 (os.Root symlink
following on Unix).

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-09 11:13:32 +02:00
Sacha Al Himdani
9371d58747 Release @probo/n8n-nodes-probo/v0.201.0
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-09 10:57:37 +02:00
Sacha Al Himdani
e8bd8e486a Release probod/v0.224.0
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-09 10:55:32 +02:00
Bryan Frimin
f20c3d73d2 Verify signature ownership in esign accept/record-event flows
Any self-provisioned trust center visitor could accept another
visitor's NDA signature or inject audit-trail events into it by
supplying its GID, since AcceptSignature and RecordEvent trusted the
client-supplied signature ID without checking it belonged to the
caller (GHSA-22xj-f767-ppw6). SignerEmail/ActorEmail are always
derived from the verified session identity, never client input, so
comparing them against the signature's stored SignerEmail in
pkg/esign/service.go closes the hole at its root without touching the
resolver-level authorization already in place elsewhere.

Adds an e2e regression test that self-provisions two trust center
visitors through the real magic-link flow and confirms one cannot
touch the other's signature.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-09 10:27:22 +02:00
Sacha Al Himdani
83e7b3bdd4 Fix lint issues in connect v1 resolvers
Drop the redundant embedded Resolver selector flagged by staticcheck
(QF1008) in the membership and organization permission resolvers, and
add the whitespace wsl_v5 expects around the attribute range loop and
the final return in permission.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-08 22:02:31 +02:00
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
ddfabc5940 Add FERPA and PCI DSS frameworks
Add FERPA and PCI DSS framework datasets (controls plus light/dark
logos), their logo components, and register both in the framework
selector so they can be imported like the existing frameworks.

Co-authored-by: Antoine Bouchardy <antoine@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-08 11:43:16 +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
Sacha Al Himdani
909c54033c Expand n8n document update fields
Guard the content field against empty strings so adding it in the UI
without a value no longer sends content: "" to the API, which is not
valid ProseMirror content. This matches the existing defaultApproverIds
guard.

Also expose title, classification, and document type on the update
operation. Like content, these edit the current draft version, creating
one from the latest published version when none exists.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-07 15:56:17 +02:00
Sacha Al Himdani
2955820ac6 Drop n8n createDraftVersion, add body to update
Remove the createDraftVersion document operation from the n8n node and
instead expose the document body on the update operation. Updating the
body edits the current draft version, or creates one from the latest
published version when none exists, so a dedicated draft-creation
operation is redundant.

Also correct the content field help text across the document
operations: the API expects a ProseMirror document JSON string, not
markdown.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-07 15:56:17 +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
3443e63184 Release probod/v0.223.3
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
2026-07-06 17:06:03 +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
119b20bfbc Release probod/v0.223.2
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-03 17:45:53 +02:00
Sacha Al Himdani
36c9636168 Add blank line before assignment in user test
Satisfy wsl_v5 which requires whitespace between a var block and
the following assignment statement.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-03 17:44:20 +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
Sacha Al Himdani
d359eaefaa Add code comments Cursor rule
Document that comments should be rare and short, reserved for genuinely
surprising behavior or context that cannot live in the code.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-03 15:37:29 +02:00
Bryan Frimin
83cceb5d00 Release probod/v0.223.1
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 12:14:35 +02:00
Bryan Frimin
d03f6077dd Document disclosed IDOR and GraphQL DoS advisories
Add SECURITY_NOTES.md entries for GHSA-c74x-79w6-63jh (cross-tenant
IDOR via unvalidated Finding->Risk and ProcessingActivity->DPO
references) and GHSA-prh2-g8pv-m7p9 (GraphQL alias-flooding DoS),
ahead of public disclosure.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 12:11:22 +02:00
Bryan Frimin
bea410fbb7 Style
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 11:53:57 +02:00
Bryan Frimin
b03dad70e0 Add e2e tests
Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-03 10:13:00 +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