ruvnet/ruflo · error · PasswordHashError

INVALID_ROUNDS

INVALID_ROUNDS

Error message

Bcrypt rounds must be between 10 and 20 for security and performance balance

What it means

PasswordHasher's constructor validates the bcrypt cost factor and refuses rounds outside [10, 20] with PasswordHashError INVALID_ROUNDS. Below 10, hashes become brute-forceable; above 20, hashing/verification gets slow enough to enable denial-of-service on auth paths. The default is 12.

Source

Thrown at v3/@claude-flow/security/src/password-hasher.ts:114

 * ```
 */
export class PasswordHasher {
  private readonly config: Required<PasswordHasherConfig>;

  constructor(config: PasswordHasherConfig = {}) {
    this.config = {
      rounds: config.rounds ?? 12,
      minLength: config.minLength ?? 8,
      maxLength: config.maxLength ?? 128,
      requireUppercase: config.requireUppercase ?? true,
      requireLowercase: config.requireLowercase ?? true,
      requireDigit: config.requireDigit ?? true,
      requireSpecial: config.requireSpecial ?? false,
    };

    // Validate configuration
    if (this.config.rounds < 10 || this.config.rounds > 20) {
      throw new PasswordHashError(
        'Bcrypt rounds must be between 10 and 20 for security and performance balance',
        'INVALID_ROUNDS'
      );
    }

    if (this.config.minLength < 8) {
      throw new PasswordHashError(
        'Minimum password length must be at least 8 characters',
        'INVALID_MIN_LENGTH'
      );
    }
  }

  /**
   * Validates password against configured requirements.
   *
   * @param password - The password to validate
   * @returns Validation result with errors if any

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Set rounds between 10 and 20 (12 — the default — is the usual sweet spot)
  2. Parse and range-check env-provided values before constructing the hasher
  3. If a policy truly demands >20, benchmark the verify path first and account for the latency cost on every login

Example fix

// before
new PasswordHasher({ rounds: 4 });

// after
new PasswordHasher({ rounds: 12 });
Defensive patterns

Strategy: validation

Validate before calling

const rounds = Number(process.env.BCRYPT_ROUNDS ?? 12);
if (!(rounds >= 10 && rounds <= 20)) {
  throw new Error(`bcrypt rounds must be 10-20, got ${rounds}`);
}
new PasswordHasher({ rounds });

Type guard

function isPasswordHashError(e: unknown, code?: string): boolean {
  return e instanceof Error && e.name === 'PasswordHashError'
    && (code === undefined || (e as { code?: string }).code === code);
}

Try / catch

try {
  return new PasswordHasher(cfg);
} catch (e) {
  if (isPasswordHashError(e, 'INVALID_ROUNDS')) {
    return new PasswordHasher({ ...cfg, rounds: 12 }); // fall back to the default
  }
  throw e;
}

Prevention

When it happens

Trigger: new PasswordHasher({ rounds: 4 }) copied from a benchmark or legacy config; rounds supplied via env as a string that slips through unvalidated; raising rounds above 20 for 'extra security' without benchmarking login latency.

Common situations: Configs ported from other bcrypt wrappers with lower floors; performance tuning dropping below 10; compliance-driven increases past 20 that stall auth throughput.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/9833cd7c6f75c8a2. Report an issue: GitHub.