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>
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>
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>
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>
The Organization "Get Many" operation listed all viewer profiles without
filtering by state, then selected each profile's nested organization field.
Inactive profiles (e.g. unaccepted invitations) have no active membership,
so the per-org iam:organization:get authorization check failed and the whole
operation errored.
Filter profiles to ACTIVE state, matching the `prb org list` CLI behavior, so
only organizations the identity is an active member of are fetched.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Bump @n8n/node-cli to 0.37.1 (the `^0.24.1` range was trapping it at
0.24.x), which pulls an updated toolchain and clears the eslint@8,
glob@7, inflight, rimraf@3, @humanwhocodes/*, node-domexception and
uuid@10 deprecation warnings at the source.
Add a glob override (^13.0.6) so rimraf@6 no longer pulls the
deprecated glob@11, and trim the overrides list down to the three that
actually do work (uuid, js-yaml, glob); the rest were redundant or dead.
npm audit reports 0 vulnerabilities.
The upgraded @n8n/eslint-plugin-community-nodes rules flagged two
pre-existing issues in the Probo node, now fixed: handle
continueOnFail() in execute() and throw NodeOperationError instead of
raw Error.
The only remaining warning is @langchain/community, whose entire
package is deprecated upstream and is a hard dependency of
@n8n/ai-utilities.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
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>
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>
Address PR review feedback identified by cubic:
- Re-key CardInset off the Card's resolved padding (shared via context)
instead of size, so insets line up even when padding is decoupled from
size via the padding prop; padding="none" becomes a no-op.
- Give ProboLogo a default aria-label so its role="img" SVG has an
accessible name when used standalone; callers can still override it.
- Make the ProboLogo story render spread args so Storybook controls work.
Signed-off-by: Émile Ré <emile@probo.com>
Add the frameworks (Compliance), security commitments, trusted-by, and
recent updates sections to the home page, plus a site-wide "Powered by
Probo" footer. Frameworks, references, and updates are relay-driven; the
security commitments section uses placeholder POJO data until a backend
exists.
Keep generic primitives in the v2 kit (ProboLogo, a faint Text tone, and
a soft Card variant with padding="none") and place the product-specific
cards under the app's components folder, composing the base Card. Resolve
each framework's themed logo in the graph via Framework.themedLogoUrl,
mirroring the trust center logo resolver.
Signed-off-by: Émile Ré <emile@probo.com>
Fix the valid issues raised in the scaffold review.
UI kit: the Button loading state now replaces only the leading icon
instead of dropping the label, Button consumes the `active` variant so
it no longer leaks onto the DOM, and every v2 skeleton sets aria-hidden
after the prop spread so a consumer cannot override it.
@probo/relay: guard the caller-supplied onCompleted/onError callbacks so
a throwing callback still settles the awaitable mutation promise instead
of leaving it pending.
compliance-portal: normalize external website hrefs and read hostname
via URL.hostname, add a localized catch-all not-found route, and widen
the .gitattributes glob so colocated __generated__ artifacts at any depth
are marked generated.
Docs: correct the forms guide (Base UI passes plain values, Zod v3
flatten API), spread the child fragment in the permissions example, and
drop references to v2 components that do not exist in the ui guide.
Signed-off-by: Émile Ré <emile@probo.com>
The popup stacked a CSS border on top of shadow-5, whose first layer is
already a 0 0 0 1px sand-a3 hairline ring, so the edge rendered as two
1px lines. Drop the redundant border and let the shadow provide the
single hairline.
Also transition the scale property instead of transform: Tailwind v4
maps scale-95 to the CSS scale property, so the old transform,opacity
list never animated the scale and the close snapped. Animate scale and
opacity with an explicit 150ms ease-out so open and close are smooth.
Signed-off-by: Émile Ré <emile@probo.com>
Soften the popup border to the translucent sand-a2 token and inset the
separator (mx-3) so it lines up with item text instead of bleeding to
the panel edges, lightening it to sand-a3. Both now track the Figma
alpha hairlines and keep their dark-mode values via the Radix alpha
scale.
Also modernize the existing arbitrary-variant classes in this file to
Tailwind's shorthand syntax (data-[x]: -> data-x:, origin-[var(...)] ->
origin-(...)).
Signed-off-by: Émile Ré <emile@probo.com>
Add a TrustCenter.themedLogoUrl Relay live resolver that reads the
light and dark logos from the graph and subscribes to the system
prefers-color-scheme media query, returning the dark logo (falling
back to the light one) when the OS prefers dark.
The TopBar now selects this single field instead of pulling both logo
URLs and branching on useSystemTheme, keeping the theme-to-URL logic in
the graph where any component can reuse it.
Signed-off-by: Émile Ré <emile@probo.com>
DropdownGroupLabel renders Base UI's Menu.GroupLabel, which requires a
Menu.Group ancestor. The user menu rendered the label directly in the
popup, so opening it threw a missing MenuGroupContext error. Wrap the
email label in a DropdownGroup.
Signed-off-by: Émile Ré <emile@probo.com>
The dark-mode script set the .dark class once at load. Subscribe to the
prefers-color-scheme media query so the theme also updates while the page
is open when the OS appearance changes.
Signed-off-by: Émile Ré <emile@probo.com>
TextSkeleton and HeadingSkeleton baked w-full into their shared base.
Under tailwind-variants/lite (no tailwind-merge) that collided with the
caller's width class and won by stylesheet order, so every skeleton
ignored its width: bars stretched full in column contexts and collapsed
in flex rows.
Drop w-full from the base so the single caller width applies, matching
ButtonSkeleton/AvatarSkeleton. Size the compliance-portal TopBar and
Hero skeleton bars to roughly match their English copy, and add a rule
so skeleton widths are revisited when en-US strings change.
Signed-off-by: Émile Ré <emile@probo.com>
The solid color rules in the Button, IconButton, and Badge variant
matrices had no highContrast condition, so a high-contrast solid element
matched both the base rule and the high-contrast rule. With
tailwind-variants/lite (no tailwind-merge) both classes are emitted, and
a static text-white from the base wins over the theme-aware text step.
That looked fine in light mode but left illegible white text on the
light step-12 background once dark mode was enabled.
Gate the base solid rules on highContrast:false so the self-contained
high-contrast rules are authoritative. The tinted variants are left as
is: their step-11/step-12 text both flip with the theme, so they are
never illegible.
Signed-off-by: Émile Ré <emile@probo.com>
Add a synchronous head script to the compliance portal that sets the
.dark class on <html> when the OS prefers a dark scheme, so the v2 Radix
theme resolves to dark values before first paint with no flash. Declare
a color-scheme meta so native UI matches the active scheme.
Signed-off-by: Émile Ré <emile@probo.com>
Replace the remaining hard-coded UI copy in the top bar, user menu, home
hero, and the documents/subprocessors/updates/requests pages with i18next
keys, and supply the en-US and fr-FR catalog entries for them. The home
hero title uses value interpolation rather than string concatenation so
word order stays correct across locales.
Signed-off-by: Émile Ré <emile@probo.com>
Wire i18next into the compliance portal with a custom backend built on
import.meta.glob, so each _locales/*.json becomes its own lazily loaded
chunk keyed by a namespace derived from the folder path. The active
language is resolved from the browser, collapsing any fr*/en* tag to
fr-FR/en-US with en-US as the ultimate fallback; fallbackLng then only
covers individual missing keys.
Add an app-level default namespace catalog and switch the documents
page title to a translation key to exercise the lazy-loading path.
Signed-off-by: Émile Ré <emile@probo.com>
On a hard reload the root route briefly showed a generic centered pulse
(PageSkeleton) while the layout chunk downloaded, then snapped to the
topbar skeleton. Point the root Fallback at MainLayoutSkeleton so the
chunk-load fallback matches the loader's own skeleton and the transition
is seamless. PageSkeleton is now unused and removed.
Signed-off-by: Émile Ré <emile@probo.com>
Build the Trust Center landing hero and the nav page headers on a shared
HeaderBand shell. The home page loads its own query and feeds the Hero
(org headline, description, and contact info) via a colocated fragment;
the Documents, Subprocessors, Updates, and Data Requests pages use a
PageHeader (size-7 title, optional count, inline actions). Switch the
layout body to the grey sand-2 canvas so the white header bands read as
surfaces on top.
Split the header UI into focused components rather than one
over-configurable Hero: HeaderBand owns the band, Hero the landing
content, PageHeader the nav-page content. Add a hostname helper under
lib/url and standardize the 1024px container on the max-w-5xl token.
The Documents/Subprocessors filter/search/tabs toolbars and item counts
are deferred until the matching v2 components and queries exist.
Signed-off-by: Émile Ré <emile@probo.com>
Capture two conventions surfaced while building the top bar: use the
Relay @required directive to make expected-present nullable fields
non-null for consistent typing, and reserve default exports for the
component that lazy() imports as a bundle entry while everything else
uses named exports.
Signed-off-by: Émile Ré <emile@probo.com>
Build the Trust Center top navigation in a Relay-wired layout route. The
portal now mounts a Relay environment and provider, and the root layout
loads a query whose fragment feeds the TopBar: brand, ghost-pill nav with
an active state from the router, and a guest "Get Access" button versus an
authenticated user menu. Placeholder section routes keep the nav links and
active state functional until real pages land.
Wire the Relay tagged-template transform via @rolldown/plugin-babel and
set the router basename to the /trust/{slug} path prefix so the app
resolves under its served path.
Signed-off-by: Émile Ré <emile@probo.com>
Round out the button family with the two navigational siblings described
in the UI guide: Anchor renders a styled <a> for external links and Link
renders a react-router link for in-app navigation, both sharing Button's
tv styles. Add an `active` look variant so nav items can carry a
persistent selected state without forking structure.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Card component from the Probo Radix UI Figma: a <div>
container with size 1-5 (padding + radius), the surface/classic/ghost
variants, and an interactive hover/active affordance. Add a CardInset
part that bleeds content (e.g. a cover image) to the card edges,
reading the card size from context so it negates the matching
padding. Includes a paired CardSkeleton. Styling uses
tailwind-variants/lite.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Dropdown Menu family from the Probo Radix UI Figma on
top of Base UI's Menu primitive, exported as flat Dropdown* parts:
Dropdown (root), DropdownTrigger, DropdownPopup, DropdownItem (with
shortcut and accent/error color), DropdownCheckboxItem,
DropdownRadioGroup/RadioItem, DropdownGroup/GroupLabel,
DropdownSeparator, and DropdownSubmenu/SubmenuTrigger.
The popup carries size (1-2), variant (solid/soft highlight), and
highContrast to its items through a small context. Styling uses
tailwind-variants/lite with data-[highlighted] selectors; item
shortcuts inherit the contrast color on highlight.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Callout component from the Probo Radix UI Figma: a
short contextual message with a leading icon (defaults to an info
icon), with size 1-3, the soft/surface/outline variants across the
sand/gold/red/green/amber/sky scales, and highContrast. Includes a
paired CalloutSkeleton. Styling uses tailwind-variants/lite slots
(root/icon/text).
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Badge component from the Probo Radix UI Figma: a
non-interactive <span> with size 1-3, the solid/soft/surface/outline
variants across the sand/gold/red/green/amber/sky scales with
highContrast, and an optional iconStart. Corner radius is bound to
size. Includes a paired BadgeSkeleton. Styling uses
tailwind-variants/lite.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the IconButton component from the Probo Radix UI Figma: a
square, icon-only <button> sharing Button's size 1-4 and
variant/color/highContrast matrix, with a loading spinner state. The
single icon is passed as children; callers provide an aria-label.
Corner radius is bound to size. Includes a paired IconButtonSkeleton.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Button component from the Probo Radix UI Figma: a
<button> with size 1-4, the classic/solid/soft/surface/outline/ghost
variants across the sand/gold/red/green/amber/sky scales with
highContrast, iconStart/iconEnd slots, and a loading state that
shows only a spinner. Corner radius is bound to size per the design
(radius is theme-global, not a per-instance prop). Includes a paired
ButtonSkeleton. Styling uses tailwind-variants/lite.
Signed-off-by: Émile Ré <emile@probo.com>