Back to Blog
high SEVERITY6 min read

How Weak bcrypt Salt Rounds Happen in Node.js and How to Fix It

A critical password hashing weakness was discovered in the authentication controller where bcrypt was configured with only 10 salt rounds instead of the recommended minimum of 12. This configuration made user passwords significantly more vulnerable to brute-force attacks if an attacker gained access to the password hash database. The fix was a simple but impactful one-line change that doubles the computational cost required to crack passwords.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 16, 2026•Reviewed August 16, 2026

Answer Summary

This vulnerability involves insufficient bcrypt salt rounds (CWE-916) in a Node.js authentication controller. The `saltRounds` constant was set to 10, which provides inadequate protection against modern GPU-based password cracking. The fix increases `saltRounds` from 10 to 12 in `authController.js`, quadrupling the computational work required to crack each password hash.

Vulnerability at a Glance

cweCWE-916
fixChanged `saltRounds` from 10 to 12 in `backend/controllers/authController.js`
riskAttackers can crack weak passwords 4x faster with salt rounds of 10 vs 12
languageJavaScript (Node.js)
root cause`saltRounds = 10` provides insufficient computational cost against GPU attacks
vulnerabilityInsufficient Password Hashing Work Factor

Introduction

The backend/controllers/authController.js file handles all user authentication logic including registration and login, but a subtle configuration flaw in the password hashing setup created a significant security risk. On line 10, the saltRounds constant was set to 10—a value that was once considered adequate but now falls short against modern GPU-accelerated password cracking techniques.

This matters because if an attacker ever gains access to your MongoDB database through a separate vulnerability (SQL injection, misconfigured access controls, or a data breach), the strength of your password hashes becomes your last line of defense. With salt rounds of 10, that defense was weaker than it should be.

The Vulnerability Explained

bcrypt is a password hashing function designed to be computationally expensive, making brute-force attacks impractical. The "salt rounds" parameter (also called the work factor or cost factor) determines how many iterations the algorithm performs. Each increment doubles the computational work required.

Here's the vulnerable configuration that was in production:

const saltRounds = 10;

At first glance, this seems reasonable—10 salt rounds was the standard recommendation for years. However, the security landscape has changed dramatically:

Why 10 Salt Rounds Is No Longer Sufficient

The computational cost of bcrypt with 10 rounds can be expressed as 2^10 = 1,024 iterations. Modern GPUs can test millions of password candidates per second at this work factor. According to security research, a single high-end GPU can crack bcrypt hashes at approximately:

  • 10 rounds: ~5,000 hashes/second
  • 12 rounds: ~1,250 hashes/second

This means an attacker with access to your password hashes could crack weak passwords (common words, short passwords, predictable patterns) in hours rather than days.

Attack Scenario Specific to This Application

Consider this exploitation path for the affected application:

  1. Database Access: An attacker exploits a MongoDB injection vulnerability or gains access through a misconfigured cloud database instance
  2. Hash Extraction: They dump the users collection containing bcrypt password hashes
  3. Offline Cracking: Using a GPU cluster, they run dictionary attacks against the hashes
  4. Account Takeover: With salt rounds of 10, common passwords like "Summer2024!" or "Company123" crack within hours
  5. Persistent Access: Given the TOKEN_EXPIRY = "7d" setting visible in the code, compromised accounts provide week-long access windows

The combination of the 7-day JWT expiration and weak password hashing created a compounding risk—attackers had both easier password cracking AND longer exploitation windows.

The Fix

The fix was elegantly simple—a single character change with significant security implications:

Before

const saltRounds = 10;

After

const saltRounds = 12;

This change in backend/controllers/authController.js at line 10 increases the computational cost by a factor of 4 (2^12 / 2^10 = 4). Here's what this means in practice:

Metric Salt Rounds 10 Salt Rounds 12 Improvement
Iterations 1,024 4,096 4x more work
GPU crack rate ~5,000/sec ~1,250/sec 4x slower
Time to crack 1M hashes ~3.3 minutes ~13.3 minutes 4x longer

Why This Specific Change Works

The fix preserves complete backward compatibility—existing password hashes remain valid and users don't need to reset their passwords. When users next log in and their credentials are verified, new password changes will use the stronger work factor. Over time, as users update their passwords, the entire database migrates to the stronger configuration.

The change is also forward-looking. OWASP currently recommends a minimum of 10 rounds but suggests 12+ for sensitive applications. By choosing 12, this fix aligns with current best practices while maintaining acceptable login performance (bcrypt with 12 rounds typically completes in 200-400ms on modern server hardware).

Key Takeaways

  • The saltRounds = 10 configuration in authController.js was a silent vulnerability—the code worked correctly but provided insufficient protection against modern attacks
  • Each bcrypt salt round increment doubles computational cost—going from 10 to 12 rounds makes password cracking 4x harder
  • The 7-day JWT expiration (TOKEN_EXPIRY = "7d") amplified the risk—compromised passwords provided extended access windows
  • Existing password hashes remain valid after the fix—no user disruption, gradual security improvement as passwords are updated
  • Static analysis can catch this pattern—tools can flag bcrypt configurations below threshold values

How Orbis AppSec Detected This

  • Source: User password input during registration/login flows in authController.js
  • Sink: bcrypt.hash() call using saltRounds constant set to 10
  • Missing control: Insufficient work factor configuration—salt rounds below the recommended minimum of 12
  • CWE: CWE-916 (Use of Password Hash With Insufficient Computational Effort)
  • Fix: Increased saltRounds from 10 to 12 in backend/controllers/authController.js:10

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates how security requirements evolve over time. Code that was secure when written can become vulnerable as attack capabilities improve. The bcrypt salt rounds configuration of 10 was once the standard recommendation, but GPU advances have made it insufficient for protecting against determined attackers.

The fix—changing a single number from 10 to 12—quadruples the difficulty of password cracking while maintaining full backward compatibility. It's a reminder that security isn't just about avoiding obvious mistakes; it's about staying current with evolving best practices.

Review your own authentication code today. If you're using bcrypt with fewer than 12 salt rounds, you have a similar vulnerability waiting to be exploited.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #26

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.