Skip to content

Notification management token hash stored without salting, enabling offline brute-force via database read access #8281

Description

@atul-upadhyay-7

Describe the bug

The /api/notify route (app/api/notify/route.ts) issues and verifies a notification management token to allow users to update or delete their notification preferences without re-authenticating via GitHub OAuth.

The token is hashed before storage using hashNotificationManagementToken (from lib/notification-management-token.ts). However, inspection of the flow reveals two compounding problems:

  1. No per-record salt is used when hashing. If the underlying hash function is a fast, unsalted algorithm (e.g. plain SHA-256 without a cost factor), an attacker who obtains a dump of the Notification collection can pre-compute a rainbow table over the short token space and recover tokens in bulk offline.

  2. The managementTokenHash field is excluded from normal projections (select('+managementTokenHash') is called explicitly), but this exclusion only prevents accidental exposure through application logic — it offers no protection against database-level access (e.g. a misconfigured Atlas IP allowlist, a compromised admin credential, or a MongoDB injection vulnerability elsewhere in the app).

Once an attacker recovers a valid token, they can silently change or delete another user's notification email address by calling POST /api/notify or DELETE /api/notify with the Authorization header set to the recovered token — no GitHub OAuth interaction required.

Steps to Reproduce

  1. Register notification preferences for a victim account via POST /api/notify. Capture the managementToken returned in the response.
  2. Obtain a read-replica or backup dump of the notifications collection (simulating insider access or a misconfigured Atlas cluster).
  3. Run an offline dictionary/brute-force attack against the stored managementTokenHash values.
  4. Use a recovered token to silently overwrite the victim's email via POST /api/notify — the server returns 200 without any additional verification.

Expected Behavior

  • Token hashes should be produced with a slow, salted KDF (e.g. bcrypt, argon2id, or scrypt) so that offline brute-force is computationally infeasible.
  • Alternatively, tokens should be long enough (≥ 128 bits of entropy) that brute-force is impractical regardless of the hash algorithm.

Affected Code

lib/notification-management-token.tshashNotificationManagementToken and verifyNotificationManagementToken functions.

app/api/notify/route.ts, lines 198–204 — token issuance and hash storage path.

Impact

An attacker with read access to the MongoDB notifications collection (e.g. through a misconfigured connection string, a backup leak, or an injection flaw) can recover management tokens and hijack notification preferences for any registered user, including redirecting commit/streak/milestone alert emails to an attacker-controlled address.

Suggested Fix

Replace the current hash function with bcrypt (cost ≥ 12) or argon2id:

import bcrypt from 'bcryptjs';

export function hashNotificationManagementToken(token: string): string {
  return bcrypt.hashSync(token, 12); // salted, adaptive cost
}

export function verifyNotificationManagementToken(token: string | null, hash: string | null): boolean {
  if (!token || !hash) return false;
  return bcrypt.compareSync(token, hash);
}

Environment

  • Next.js 15 (App Router)
  • MongoDB (Mongoose) data layer
  • Deployed on Vercel (production)

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions