zypher.co / reports / getconnects-cross-tenant-data-access signed in · client link
Penetration test report

GetConnects

F-01: Unauthenticated cross-tenant data access
April 2026 Delivered 2026-04-23
Risk index
98
/ 100
Severity distribution
Critical
1
High
0
Medium
0
Low
0
Info
0
Executive summary

The /api/segment/evaluate endpoint accepts an organisationId in the request body and returns matching records for that tenant. It does not require authentication and does not verify the caller's relationship to the requested organisation. A single POST from any internet host returns up to the honoured limit value (observed at 100,000 records in one request). Combined with F-04 (an org-ID enumeration primitive that lives in the UI), this is a full, unauthenticated, drive-by read of any tenant on the platform.

Finding 1 of 5 from the GetConnects multi-tenant API assessment. Delivered , published .

Engagement
Client
GetConnects
Endpoint
POST /api/segment/evaluate
Class
BOLA (OWASP API1)
Auth required
None
CVSS vector
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Status
Reported, remediation and retest cycle in progress
Redaction notice. The API host, tenant identifiers, and contact record contents in the response bodies have been genericized. Endpoint paths, request shape, severity, and technical findings are unmodified.

Findings

1 total
01 Critical
F-01: Unauthenticated cross-tenant data access
BOLA (OWASP API1) · POST /api/segment/evaluate
9.8 reported +
Description

The handler trusts the client-supplied organisationId. No session cookie, bearer token, or CSRF token is required to reach the endpoint or to change the target tenant. The searchCriteria block is honoured, so an attacker can either broaden matches (as in the proof-of-concept below) or narrow them to a specific target.

Impact

No session, no cookie, arbitrary identifiers accepted, and the limit parameter is honoured, so one request can pull a large slice of a tenant's contact records. F-04 provides the enumeration primitive that makes this practically exploitable without prior reconnaissance, since the analytics UI leaks valid organisation identifiers to any logged-in user. In combination, an attacker with any low-privilege account (or none at all) can walk the platform tenant by tenant.

Sensitive data recoverable through this endpoint alone includes contact names, phone numbers, and organisation memberships. Coupled with F-03, the same identifiers unlock cross-tenant SMS content and campaign metadata.

Remediation
  1. Centralise tenant scoping in a request middleware. Every handler should receive the tenant identifier from the authenticated session, never from client input. Endpoints that legitimately need cross-tenant access become explicit opt-ins with their own gate.
  2. Require authentication on /api/segment/evaluate and reject any request whose session tenant does not match the resolved target. There is no legitimate unauthenticated use case for this endpoint.
  3. Log tenant boundary violations at the middleware layer even when a request would otherwise pass. This surfaces attempted probes against any remaining gaps.
  4. Add a CI check that runs the top ten most sensitive endpoints under three synthetic sessions (unauthenticated, low-priv-A, admin-B) and fails the build if any returns data outside the caller's scope.
Evidence

Proof-of-concept request:

POST /api/segment/evaluate HTTP/2
Host: [REDACTED]
Content-Type: application/json

{
  "json": {
    "organisationId": "<target_org_id>",
    "searchCriteria": {
      "and": [
        { "or": [
          { "field": "firstName", "operator": "contains", "value": "a" }
        ]}
      ]
    },
    "limit": 100000,
    "skip": 0
  }
}

Response, shape only:

HTTP/2 200 OK
Content-Type: application/json

{
  "json": {
    "organisationId": "<target_org_id>",
    "count": 100000,
    "records": [
      { "firstName": "[Redacted]", "lastName": "[Redacted]",
        "mobile": { "normalised": "+XX XXX XXX XXX" },
        "organisation": "[Tenant name redacted]" },
      // 99,999 more contact records for a tenant we do not belong to
    ]
  }
}

Root cause

  • Authorization is not enforced at the request-middleware layer, so per-handler consistency is required. This handler was written without the check.
  • The tenant identifier is taken from the request body rather than derived from the authenticated session, so there is no source of truth to compare against.
  • The endpoint does not require authentication at all, so even the identity that a check would have compared to does not exist on the request.

Timeline

DateEvent
Engagement window; finding delivered on .
Following weeksRemediation and retest cycle.
Public release of redacted writeup.
Remediation in progress Delivered 2026-04-23 Published 2026-07-12
More reports →