Skip to content

fix(mint): reject cross-org installation mismatch#1332

Open
ralphbean wants to merge 1 commit into
mainfrom
fix/cross-org-installation-mismatch
Open

fix(mint): reject cross-org installation mismatch#1332
ralphbean wants to merge 1 commit into
mainfrom
fix/cross-org-installation-mismatch

Conversation

@ralphbean
Copy link
Copy Markdown
Contributor

Summary

  • When the same GitHub App is installed on multiple orgs sharing a mint, findInstallation could return an installation belonging to a different org than the OIDC-authenticated one. The mint used it without verifying ownership.
  • Added an assert in findInstallation that inst.Account.Login matches the expected org. Mismatches now return 502 with a clear log message instead of silently minting a cross-org token.
  • Developed via red-green TDD: TestHandler_CrossOrgInstallationMismatch reproduces the Triage agent should not label ready-to-code when it identifies an existing PR that addresses the issue #1321 bug and confirms the fix rejects it.

Refs: #1321, #737, #672

Test plan

  • TestHandler_CrossOrgInstallationMismatch — fails before fix, passes after
  • TestHandler_CrossOrgInstallation_SameOrgPasses — confirms happy path still works
  • Full mint test suite passes
  • Embedded copy synced to mintsrc/main.go.embed

🤖 Generated with Claude Code

… org

When the same GitHub App is installed on multiple orgs sharing a mint,
findInstallation could return an installation belonging to a different
org than the one authenticated via OIDC. The mint used this installation
without verifying ownership, potentially minting tokens scoped to the
wrong org's repos.

Assert that the installation's Account.Login matches the expected org
derived from the OIDC token. If they differ, reject the request with a
502 and a clear log message rather than silently minting a cross-org
token.

Refs: #1321, #737, #672

Assisted-by: Claude claude-opus-4-6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Ralph Bean <[email protected]>
@github-actions
Copy link
Copy Markdown

Site preview

Preview: https://2a57f99b-site.fullsend-ai.workers.dev

Commit: dcd73ea62a12b024e36572dcc49f73ebf9eb8f7c

@fullsend-ai-review
Copy link
Copy Markdown

Review

Findings

No findings.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label May 21, 2026
Copy link
Copy Markdown
Contributor

@waynesun09 waynesun09 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Squad (10 agents) — 3 MEDIUM findings

Sound defense-in-depth security fix. The EqualFold check and error handling are correct (error never leaks to HTTP client). Main concerns: (1) PR description overstates the threat — GitHub API should not return cross-org installations given the URL construction, so this is a guardrail, not a fix for a routinely exploitable path; (2) missing a case-variation test to document the EqualFold intent; (3) security event only logged via log.Printf with no structured alerting.

Comment thread internal/mint/main.go
Comment on lines +748 to +751
// Verify the installation belongs to the expected org. When the same
// GitHub App is installed on multiple orgs, the API could return an
// installation for the wrong org. Accepting it would mint a token
// scoped to a different org's repos.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Threat model description overstates the risk

The comment says "the API could return an installation for the wrong org," but GET /repos/{owner}/{repo}/installation constructs the URL using org from the OIDC-validated repository_owner claim, so GitHub should only return the installation for that specific org (or 404). The scenario where Account.Login differs from {owner} is not documented GitHub behavior — realistic vectors are more like repo transfers or org renames, not routine cross-org installation leakage.

The defense-in-depth check is still valuable and worth keeping, but consider clarifying the comment to say this guards against hypothetical API inconsistencies rather than a known, routinely exploitable path.

6/10 review agents flagged this

Comment thread internal/mint/main.go
// installation for the wrong org. Accepting it would mint a token
// scoped to a different org's repos.
if !strings.EqualFold(inst.Account.Login, org) {
log.Printf("installation org mismatch: expected %s, got %s (installation %d for %s/%s)",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — No structured alerting for this security-relevant event

A cross-org installation mismatch is a security event (misconfigured App or potential exploitation attempt), but it's only logged via log.Printf — easily lost in Cloud Functions log volume. This is the sole detection layer for this attack path.

Consider tagging the log with a severity prefix (e.g., "SECURITY: installation org mismatch") that Cloud Logging alerting policies can match on, or incrementing a custom metric (mint_cross_org_mismatch_total) so a threshold-based alert fires.

2/10 review agents flagged this

w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(installationTokenResponse{
Token: "ghs_correct_org_token",
ExpiresAt: "2026-05-07T12:00:00Z",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Missing test for case-mismatch between org and Account.Login

The code uses strings.EqualFold but tests only use exact-case matches ("org-a" vs "org-b", "org-a" vs "org-a"). GitHub returns canonical-cased logins (e.g., "Org-A") while org is lowercased at line 508. Adding a test where this mock returns Login: "Org-A" (mixed case) and verifying minting still succeeds (200) would document the EqualFold intent and guard against a future regression if someone changes it to ==.

4/10 review agents flagged this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants