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>
7.4 KiB
Security Notes
User-facing notes on security-relevant changes to Probo. For the vulnerability reporting process, see SECURITY.md.
Privilege escalation to OWNER via createUser
2026-07-09, IAM
Promoting a membership to OWNER is an owner-only action, and
updateMembership enforced it. The createUser mutation (and its MCP
CreateUserTool twin) created a membership with a caller-chosen role
but only authorized iam:membership-profile:create, which ADMIN holds,
and never re-checked the owner-only gate. An organization ADMIN could
therefore call createUser with role: OWNER and an attacker-controlled
email, activate it through the normal invitation flow, and gain full
OWNER privileges (organization deletion, SAML/SSO and SCIM configuration,
owner promotion/demotion) that ADMIN is explicitly denied.
Both createUser entry points now enforce the owner-only authorization
when the requested role is OWNER, mirroring updateMembership. Ownership
grants were subsequently consolidated into fail-closed allow/deny IAM
policies (keyed on the requested target_role) so that granting OWNER
fails closed across every membership-creating and membership-updating
path.
Reported by Pig-Tail. (GHSA-cppp-g98f-gfpp)
Cross-tenant and hidden-item disclosure via Query.node in the Trust Center API
2026-07-09, Trust Center
The public Trust Center GraphQL API exposed Query.node(id:) with
optional authentication. Six of its type branches (Organization,
Framework, Audit, ThirdParty, TrustCenter, and
TrustCenterReference) derived their tenant scope from the
client-supplied GID and loaded the object with no comparison to the
visited trust center's organization and no visibility filter. An
unauthenticated visitor who held a target GID could read items hidden
from that trust center, and objects belonging to any other organization
(including orgs with no published trust center), leaking names,
descriptions, contact details, and connected items.
Every trust API resolver now derives its scope from the active
compliance page's organization via compliancepage.ScopeFromContext,
so reads are confined to the page's tenant. Cross-tenant or unknown IDs
surface as not-found instead of leaking data.
Reported by Pig-Tail. (GHSA-w23w-f7v2-625w)
Broken access control in the public e-signature NDA API
2026-07-09, Trust Center
The public Trust Center mutations acceptElectronicSignature and
recordSigningEvent were gated only by session presence. The underlying
esign service built its tenant scope from the client-supplied
signatureId and never verified the signature belonged to the caller.
Any self-provisioned trust center visitor holding another visitor's
signature GID could complete that visitor's NDA signature (overwriting
signer name, IP, and user agent, and triggering certificate sealing) or
inject arbitrary audit-trail events, defeating the non-repudiation and
integrity guarantees of the e-signature system.
The esign signature operations now take a caller-provided scope rather
than deriving one from the requested ID, and AcceptSignature /
RecordEvent verify signature ownership within scope before acting. The
signer and actor emails are always derived from the verified session
identity, never client input, and are compared against the signature's
stored SignerEmail.
Reported by Pig-Tail. (GHSA-22xj-f767-ppw6)
Cross-tenant IDOR via unvalidated foreign-key references
2026-07-03, GraphQL
Two console GraphQL resolvers authorized the parent object and then resolved a related object with a tenant scope taken from that related object's own GID, instead of from the authorization result. An authenticated member of one organization could attach another organization's Risk or Data Protection Officer profile GID to their own Finding or Processing Activity, then read it back:
Finding.riskdisclosed another organization's Risk (name, description, treatment, category, severity, owner).ProcessingActivity.dataProtectionOfficerdisclosed another organization's person profile PII (full name, email addresses, position).
FindingService and ProcessingActivityService now validate the
referenced Risk and Data Protection Officer profile against the
caller's scope before storing the reference, and the affected
resolvers (along with every other resolver following the same
pattern) now authorize the related object's own ID rather than the
parent's.
Reported by Pig-Tail. (GHSA-c74x-79w6-63jh)
GraphQL alias-flooding denial of service
2026-06-29, GraphQL
The GraphQL endpoints (connect, console, and trust) built their
server with no limits on query size or complexity. A single request
containing thousands of aliased resolver calls (e.g. a1: viewer { id } repeated thousands of times) was parsed, executed, and marshalled
in full, letting an attacker drive excessive CPU and memory use and
degrade service for other tenants.
The shared GraphQL handler now enforces a parser token limit that
rejects oversized queries before execution, a fixed query complexity
limit, and an LRU query cache, with limits configurable per
environment via PROBOD_API_GRAPHQL_* env vars and Helm values.
Reported by Muthu-Devarajan. (GHSA-prh2-g8pv-m7p9)
Open redirect bypass in saferedirect
2026-05-26, Auth
Relative redirect URLs are now normalized before validation. Paths
containing backslashes (including percent-encoded %5c) are rejected,
and the cleaned path is checked for protocol-relative and backslash
prefixes.
Previously, Validate only inspected the second character of the raw
input. A path like /../\evil.com passed validation because the second
character is ., but Go's http.Redirect normalized it to /\evil.com,
which browsers can treat as an external redirect.
Reported by Fushuling and RacerZ. (GHSA-x7qq-m748-8p2c, CVE-2026-49820)
Password changes invalidate existing sessions
2026-04-29, IAM
Password changes and resets now revoke existing sessions for the identity.
Previously, rotating a password did not touch iam_sessions rows: a stolen session stayed valid until its idle TTL elapsed, so a user whose account was compromised on another device could not evict that device by changing the password.
Inside the same transaction as the password update:
- A signed-in password change revokes every other active session for the identity and keeps the caller's current session.
- A forgot-password reset revokes all active sessions for the identity. The caller is anonymous (authenticated only by the reset token), so there is no current session to preserve.
The session middleware already rejects rows with expire_reason set, so revoked sessions are kicked out on the next request without any middleware change.
Reported by emimoir.