CVE-2026-59185: Identrail Cross-Tenant IDOR via Unverified GitHub App installation_id
Any authenticated Identrail tenant can hijack another customer's GitHub App installation by supplying that customer's installation ID in a single API call, then read their private repositories.

The problem
Identrail's GitHub App connection-completion endpoint (POST /v1/workspaces/:workspace_id/projects/:project_id/github/connect/complete) accepts installation_id from the request body or the X-GitHub-Installation-ID header.
The state token is tightly scope-checked against the caller's tenant, workspace, and project. But installation_id is only checked to be a positive integer (request.InstallationID <= 0). No ownership check is made.
After the call, Identrail mints a GitHub App installation access token for the attacker-supplied ID using the platform's own App JWT, persists the binding under the attacker's workspace, and begins serving that victim org's private repository list. The impact is cross-tenant disclosure of any customer org's private GitHub repository inventory and metadata.
Proof of concept
A working proof-of-concept for CVE-2026-59185 in github.com/identrail/identrail, with the exact payload below.
POST /v1/workspaces/ATTACKER_WORKSPACE_ID/projects/ATTACKER_PROJECT_ID/github/connect/complete HTTP/1.1
Host: app.identrail.com
Authorization: Bearer <attacker_session_token>
Content-Type: application/json
{
"state": "<attacker_valid_state_token>",
"installation_id": 12345678
}The root cause is an asymmetric trust model (CWE-639 / CWE-862). The state parameter is pinned at flow-start time to {TenantID, WorkspaceID, ProjectID} and re-verified at completion (github_connect.go:818), proving the developers understood binding is required.
But installation_id carries no equivalent pin: the only guard is a positivity check at line 794.
Because Identrail's App JWT has platform-wide authority to call POST /app/installations/{id}/access_tokens for any org that has installed the app, a valid state from the attacker's own workspace is enough to clear all checks. The service then calls ListInstallationRepositories for the victim's ID and persists the connection.
Installation IDs are not secret: they appear in GitHub post-install redirect URLs (?installation_id=...), webhook payloads, and org App settings pages, making enumeration trivial. The header-based alternative (X-GitHub-Installation-ID, router.go:3582-3588) widens the attack surface further by not requiring a JSON body at all.
The v2 flag-gated path (CompleteGitHubConnector, github_connect.go:447) carries the same gap and is additionally weaker because it matches the pending connector on state value alone with no caller-scope re-check.
The fix
Upgrade to identrail v1.0.2 (commit 835e40517509d6ef5405c27fbf14f579bedff0e7). The patch captures installation_id from GitHub's signed post-install redirect at flow-start time, pins it to the pending state record alongside {TenantID, WorkspaceID, ProjectID}, and re-verifies it at completion with the same rigor as state.
Operators who cannot upgrade immediately should block direct writes to installation_id via WAF rule on that endpoint and remove or gate the X-GitHub-Installation-ID header path.
Related research
- high · 7.5CVE-2026-52799CVE-2026-52799: Gogs Missing Authorization in Attachment Download
- highCVE-2026-52812CVE-2026-52812: Gogs LFS Cross-Tenant Object Disclosure via Dedupe Shortcut
- high · 8.6CVE-2026-72789CVE-2026-72789: SiYuan Encrypted Notebook Publish Access Bypass
- critical · 9CVE-2026-73842CVE-2026-73842: OpenChoreo cluster-gateway Missing Authentication on Internal Proxy