In apps/backend/src/routes/auth.ts, the GitHub OAuth callback (and the equivalent Google callback) handles the "no userIdentity found" case by looking up existingAccount by email and, if found, attaches the new provider identity to that account:
const existingAccount = await app.prisma.user.findUnique({ where: { email } });
if (existingAccount) {
await app.prisma.userIdentity.create({
data: { userId: existingAccount.id, provider: 'github', providerId: githubUser.id.toString() }
});
user = existingAccount;
}
For GitHub, email is derived from githubUser.email or, if null, the first primary && verified entry from /user/emails. The verified-email check on GitHub's side is trusted implicitly. However, there's a deeper issue: this account-linking happens with no re-authentication or confirmation step from the existing account owner. Any user who can get GitHub to report a given verified email (e.g. via GitHub's email-visibility settings, or a GitHub account where they've added/verified an email that matches a DevCard user's email — GitHub allows adding emails and they become "verified" once the GitHub-side confirmation email is clicked, which an attacker can do for emails they control or, in cases of stale/abandoned email addresses, emails they've taken over) can have their userIdentity silently attached to an arbitrary pre-existing DevCard account matching that email.
Once linked, that GitHub account becomes a permanent additional login method for the victim's DevCard account (issuing fresh accessToken/refreshToken cookies for existingAccount.id), with no notification email, no audit log entry distinguishable from normal login, and no requirement that the user prove control of the DevCard account being linked (e.g. by being logged in already, or confirming via the original auth method).
This is an account-takeover-via-identity-linking pattern: the trust boundary between "I proved I own this email on GitHub right now" and "therefore I should be granted standing access to whatever DevCard account already used that email" is not validated against the current session state of the DevCard account.
Affected: apps/backend/src/routes/auth.ts — both github/callback and google/callback handlers, in the else branch following the if (identity) check (the existingAccount lookup-and-link block).
In
apps/backend/src/routes/auth.ts, the GitHub OAuth callback (and the equivalent Google callback) handles the "nouserIdentityfound" case by looking upexistingAccountbyemailand, if found, attaches the new provider identity to that account:For GitHub,
emailis derived fromgithubUser.emailor, if null, the firstprimary && verifiedentry from/user/emails. The verified-email check on GitHub's side is trusted implicitly. However, there's a deeper issue: this account-linking happens with no re-authentication or confirmation step from the existing account owner. Any user who can get GitHub to report a given verified email (e.g. via GitHub's email-visibility settings, or a GitHub account where they've added/verified an email that matches a DevCard user's email — GitHub allows adding emails and they become "verified" once the GitHub-side confirmation email is clicked, which an attacker can do for emails they control or, in cases of stale/abandoned email addresses, emails they've taken over) can have theiruserIdentitysilently attached to an arbitrary pre-existing DevCard account matching that email.Once linked, that GitHub account becomes a permanent additional login method for the victim's DevCard account (issuing fresh
accessToken/refreshTokencookies forexistingAccount.id), with no notification email, no audit log entry distinguishable from normal login, and no requirement that the user prove control of the DevCard account being linked (e.g. by being logged in already, or confirming via the original auth method).This is an account-takeover-via-identity-linking pattern: the trust boundary between "I proved I own this email on GitHub right now" and "therefore I should be granted standing access to whatever DevCard account already used that email" is not validated against the current session state of the DevCard account.
Affected:
apps/backend/src/routes/auth.ts— bothgithub/callbackandgoogle/callbackhandlers, in theelsebranch following theif (identity)check (theexistingAccountlookup-and-link block).