Skip to content

Security: Fix 5 L3 Rate-Limiting, DoS, and OAuth Takeover Vulnerabilities - #523

Open
Prathvikmehra wants to merge 6 commits into
vishnukothakapu:mainfrom
Prathvikmehra:fix/security-l3-suite
Open

Security: Fix 5 L3 Rate-Limiting, DoS, and OAuth Takeover Vulnerabilities#523
Prathvikmehra wants to merge 6 commits into
vishnukothakapu:mainfrom
Prathvikmehra:fix/security-l3-suite

Conversation

@Prathvikmehra

@Prathvikmehra Prathvikmehra commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Resolves #524

🐛 Bug Description

I conducted a holistic architectural review of the platform's API surface and discovered a cluster of 5 distinct L3 Security and Architectural Vulnerabilities.

Specifically, multiple highly sensitive endpoints lack fundamental rate-limiting controls, leaving the Node.js event loop, database connection pool, and cloud storage vulnerable to Layer-7 Denial of Service (DoS) and brute-force oracle attacks. Furthermore, I identified a silent account takeover vector in the OAuth configuration.

Vulnerability Breakdown:

  1. OAuth Account Takeover (\lib/auth.ts): \�llowDangerousEmailAccountLinking\ is set to true. An attacker can register a victim's email, set a password, and wait for the victim to log in via Google SSO. NextAuth blindly links them, giving the attacker persistent, silent access to the victim's profile.
  2. CPU Exhaustion & Password Oracle (\�pp/api/profile/merge/request/route.ts): The merge request API verifies passwords using \�crypt.compare\ but lacks rate limits. Attackers can spam requests, exhausting the CPU (DoS) while simultaneously brute-forcing passwords.
  3. Merge Brute-Force (\�pp/api/profile/merge/complete/route.ts): Similar to the request endpoint, the completion route lacks rate limits on its bcrypt validations, allowing brute-forcing of the merge confirmation code.
  4. Cloud Resource Exhaustion (\�pp/api/profile/avatar/route.ts): The image upload route has no limits. An attacker can concurrently spam multipart form data, exhausting server memory and rapidly escalating Cloudinary billing costs.
  5. Database Connection Exhaustion & User Enumeration (\�pp/api/username/check/route.ts): The username availability checker hits both the \User\ and \UserAlias\ tables. An attacker can automate a dictionary attack to enumerate all users on the platform while simultaneously exhausting the Prisma connection pool.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@Prathvikmehra is attempting to deploy a commit to the vishnukothakapu's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Prathvikmehra

Copy link
Copy Markdown
Contributor Author

Hey @vishnukothakapu! I've already tested this entire suite locally. I applied strict rate limits to the 4 exposed routes using the existing lib/rateLimit.ts sliding-window utility and secured the NextAuth config. Let me know when you assign this to me, and I can open the massive PR immediately!

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add rate limits to several API routes, refactor account merging into a queued Prisma transaction, enforce email verification for credentials login, configure OAuth linking behavior, and use transaction-aware username availability checks during profile publishing.

Changes

API protection

Layer / File(s) Summary
Route rate-limit guards
app/api/export/..., app/api/profile/..., app/api/user/delete/route.ts, app/api/username/check/route.ts
Export routes use authenticated email keys; other routes use user or forwarded-IP keys and return HTTP 429 responses when limits are exceeded.

Account and profile integrity

Layer / File(s) Summary
Account merge transaction execution
lib/accountMerge.ts
Merge updates are queued as Prisma transaction operations covering profile fields, aliases, links, accounts, sessions, merge events, request consumption, and source deletion.
Transactional username validation
lib/profileWorkflow.ts
Profile publishing uses the shared transaction-aware username availability helper before alias creation.

Authentication policy

Layer / File(s) Summary
Provider and credential authentication rules
lib/auth.ts
Google and GitHub receive explicit account-linking settings, and credential login requires a verified email.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant APIRoute
  participant RateLimiter
  participant ExistingHandler
  Client->>APIRoute: Send API request
  APIRoute->>RateLimiter: Check email, user, or IP rate-limit key
  RateLimiter-->>APIRoute: Return allowance
  alt Request allowed
    APIRoute->>ExistingHandler: Continue route processing
    ExistingHandler-->>Client: Return route response
  else Limit exceeded
    APIRoute-->>Client: Return HTTP 429
  end
Loading

Possibly related issues

Possibly related PRs

Suggested labels: bug, type:bug, level3

Suggested reviewers: deepikagandla7456, vishnukothakapu, anushreebasics

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main security-focused changes: rate limiting, DoS mitigations, and OAuth account-linking hardening.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linkid Error Error Jul 24, 2026 8:40am

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/accountMerge.ts`:
- Around line 143-151: Update completeAccountMerge to reassign every UserAlias
currently owned by sourceUser.id to targetUser.id before prisma.user.delete
executes, not just the current username handled by the existing
userAlias.upsert. Preserve the existing conditional username upsert behavior and
ensure the bulk alias reassignment occurs within the same transaction.

In `@lib/auth.ts`:
- Line 54: Update the GitHub provider configuration in lib/auth.ts to set
allowDangerousEmailAccountLinking to false, matching the Google provider and
keeping unsafe OAuth account linking disabled.

In `@lib/profileWorkflow.ts`:
- Around line 268-273: Update the username-change guard in the profile
publishing workflow to recheck availability only when afterSnapshot.username is
non-null. Preserve the existing beforeSnapshot comparison and error behavior for
actual username values, while skipping isProfileUsernameAvailable when
publishing clears the username.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 90556bd2-40f0-43f2-8d71-b89fd0b8b9ad

📥 Commits

Reviewing files that changed from the base of the PR and between 7df378b and d4b0daf.

📒 Files selected for processing (10)
  • app/api/export/resume/route.ts
  • app/api/export/vcard/route.ts
  • app/api/profile/avatar/route.ts
  • app/api/profile/merge/complete/route.ts
  • app/api/profile/merge/request/route.ts
  • app/api/user/delete/route.ts
  • app/api/username/check/route.ts
  • lib/accountMerge.ts
  • lib/auth.ts
  • lib/profileWorkflow.ts

Comment thread lib/accountMerge.ts
Comment on lines +143 to +151
if (shouldAliasSourceUsername && sourceUser.username) {
transactionOperations.push(
prisma.userAlias.upsert({
where: { username: sourceUser.username },
update: { userId: targetUser.id },
create: { username: sourceUser.username, userId: targetUser.id },
})
);
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect UserAlias relation + onDelete and related source-owned relations
fd -e prisma --exec sh -c 'echo "== {} =="; cat -n {}'

Repository: vishnukothakapu/linkid

Length of output: 9338


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate accountMerge.ts =="
fd -a 'accountMerge.ts' .

echo
echo "== outline accountMerge.ts =="
ast-grep outline lib/accountMerge.ts || true

echo
echo "== relevant lib/accountMerge.ts lines =="
cat -n lib/accountMerge.ts | sed -n '1,280p'

echo
echo "== searches for userAlias relocation / username history ownership =="
rg -n "userAlias|UserAlias|usernameHistory|UsernameHistory|username.*history|updateMany\\(" lib prisma -S

Repository: vishnukothakapu/linkid

Length of output: 11525


Reassign source aliases before deleting the source user.

UserAlias.user is onDelete: Cascade, and completeAccountMerge only upserts the source’s current username before issuing prisma.user.delete. Any other aliases already pointing at sourceUser.id are removed with the source user, breaking existing alias URLs. Reassign source-owned aliases to targetUser.id before the delete, or change the relation to non-cascade behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/accountMerge.ts` around lines 143 - 151, Update completeAccountMerge to
reassign every UserAlias currently owned by sourceUser.id to targetUser.id
before prisma.user.delete executes, not just the current username handled by the
existing userAlias.upsert. Preserve the existing conditional username upsert
behavior and ensure the bulk alias reassignment occurs within the same
transaction.

Comment thread lib/auth.ts
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true,

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.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Tracked auth files:\n'
fd -a '.*auth.*|auth' . | sed 's#^\./##' | head -50

printf '\nlib/auth.ts outline:\n'
ast-grep outline lib/auth.ts || true

printf '\nRelevant lib/auth.ts lines:\n'
sed -n '1,120p' lib/auth.ts | cat -n

printf '\nSearch allowDangerousEmailAccountLinking:\n'
rg -n "allowDangerousEmailAccountLinking|dangerousEmailAccountLinking|Provider|github" lib src . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: vishnukothakapu/linkid

Length of output: 32504


Disable dangerous email account linking for GitHub.

lib/auth.ts:54 still enables allowDangerousEmailAccountLinking, while the Google provider is set to false. Keep GitHub disabled to avoid re-enabling the unsafe OAuth account linking behavior.

Proposed fix
-                      allowDangerousEmailAccountLinking: true,
+                      allowDangerousEmailAccountLinking: false,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
allowDangerousEmailAccountLinking: true,
allowDangerousEmailAccountLinking: false,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/auth.ts` at line 54, Update the GitHub provider configuration in
lib/auth.ts to set allowDangerousEmailAccountLinking to false, matching the
Google provider and keeping unsafe OAuth account linking disabled.

Comment thread lib/profileWorkflow.ts
Comment on lines 268 to 273
if (beforeSnapshot.username && beforeSnapshot.username !== afterSnapshot.username) {
// Recheck availability within the transaction to guard against TOCTOU races
const takenByOther = await tx.user.findFirst({
where: { username: afterSnapshot.username, NOT: { id: userId } },
});
if (takenByOther) {
const isAvailable = await isProfileUsernameAvailable(afterSnapshot.username, userId, tx);
if (!isAvailable) {
throw new Error("Username already taken");
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm ProfileSnapshot.username nullability and helper signature
rg -nP 'ProfileSnapshot' lib/profileWorkflow.ts -A12
rg -nP 'isProfileUsernameAvailable' lib/profileWorkflow.ts -A6

Repository: vishnukothakapu/linkid

Length of output: 7745


🏁 Script executed:

#!/bin/bash
# Inspect the remaining relevant implementation and Prisma schema field nullability.
sed -n '192,216p' lib/profileWorkflow.ts | cat -n
echo '--- schema username field ---'
rg -n 'model User|profileDraft|username' prisma 2>/dev/null || true

Repository: vishnukothakapu/linkid

Length of output: 2104


Short-circuit availability checking when publishing clears the username.

The guard enters when beforeSnapshot.username is non-null and different from afterSnapshot.username, but publishing with afterSnapshot.username === null still calls isProfileUsernameAvailable(null, ...) even though the helper expects string. Only run the availability re-check when the resulting username is non-null.

Proposed guard
-    if (beforeSnapshot.username && beforeSnapshot.username !== afterSnapshot.username) {
+    if (
+      beforeSnapshot.username &&
+      afterSnapshot.username &&
+      beforeSnapshot.username !== afterSnapshot.username
+    ) {
       // Recheck availability within the transaction to guard against TOCTOU races
       const isAvailable = await isProfileUsernameAvailable(afterSnapshot.username, userId, tx);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (beforeSnapshot.username && beforeSnapshot.username !== afterSnapshot.username) {
// Recheck availability within the transaction to guard against TOCTOU races
const takenByOther = await tx.user.findFirst({
where: { username: afterSnapshot.username, NOT: { id: userId } },
});
if (takenByOther) {
const isAvailable = await isProfileUsernameAvailable(afterSnapshot.username, userId, tx);
if (!isAvailable) {
throw new Error("Username already taken");
}
if (
beforeSnapshot.username &&
afterSnapshot.username &&
beforeSnapshot.username !== afterSnapshot.username
) {
// Recheck availability within the transaction to guard against TOCTOU races
const isAvailable = await isProfileUsernameAvailable(afterSnapshot.username, userId, tx);
if (!isAvailable) {
throw new Error("Username already taken");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/profileWorkflow.ts` around lines 268 - 273, Update the username-change
guard in the profile publishing workflow to recheck availability only when
afterSnapshot.username is non-null. Preserve the existing beforeSnapshot
comparison and error behavior for actual username values, while skipping
isProfileUsernameAvailable when publishing clears the username.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Fix 5 L3 Rate-Limiting, DoS, and OAuth Takeover Vulnerabilities

1 participant