mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 10:05:00 +08:00
* fix(security): prove GitHub installation ownership before binding it (GHSA-4rwq)
POST /integration/github/install bound whatever installation_id the request
body carried, as long as the state nonce belonged to the caller's tenant. The
nonce proves which tenant started the flow, not which GitHub installation that
tenant may bind — and binding hands the tenant the App's token for every
repository in the installation. A tenant could bind another organization's
installation and read its private repositories.
The GitHub App now issues an OAuth code with the post-install redirect ("Request
user authorization (OAuth) during installation"). POST /install — the
authenticated request — exchanges it and binds only an installation the
authorizing GitHub user is entitled to in full:
- a personal installation: the user must be that account;
- an organization installation: the user must already reach every repository
it covers (their count equals the App's own count, read before and after to
close a create/delete race).
The code is signed with the nonce it arrived with, so a code leaked from a
post-install URL cannot be replayed against another tenant's nonce, and the
user token is revoked once checked. Without a code the install is refused with
a message naming the GitHub App setting to enable.
Also closes a sibling on the same surface: GithubMiddleware loaded an
integration's settings before authentication using ?tenantId= ahead of the
Tenant-Id header, while the tenant guard validates only the header when one is
sent. Own tenant in the header plus a victim's in the query let the repository,
metadata, issue and sync routes act on the victim's installation. The
middleware now records who owns the settings, and GithubIntegrationTenantGuard
refuses a mismatch after authentication.
The install popup now shows why a connection was refused instead of closing
after two seconds, and handles the update/request redirects GitHub sends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(security): spend the install code at the callback, compare repository ids
Two P1 review findings on the first version of this fix, both correct:
- The post-install callback is public and signed ANY code with ANY live nonce,
so a leaked, unused victim code plus a nonce from the attacker's own tenant
still bound the victim's installation — the code binding proved nothing. The
callback now exchanges the code the moment it arrives (spending it, so it
cannot be replayed from a URL, history or log), checks entitlement there, and
hands the browser only a signed, 10-minute proof bound to this flow's nonce
and installation. The code never reaches the browser, and POST /install binds
nothing without a valid proof. When there is no proof, install_check tells
the web app why (no code / not entitled / unverifiable).
- Comparing repository COUNTS could be balanced by a member who creates
throwaway repositories and deletes them between the reads while the
repositories hidden from them remain. Entitlement for an organization
installation is now a set comparison: every repository id the App can reach
must be one the user can read (user ids read first, App ids after).
Also: installation ids beyond Number.MAX_SAFE_INTEGER are refused (Octokit
takes a number and would check a different installation than the one stored),
member install requests (setup_action=request) no longer hit a raw 400, and the
popup text is translatable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>