ruvnet/ruflo · error · PasswordHashError

HASH_FAILED

HASH_FAILED

Error message

Failed to hash password

What it means

hash() wraps the bcryptjs bcrypt.hash() call in try/catch; any underlying failure (not policy-related) is rethrown as PasswordHashError with code HASH_FAILED and the static message 'Failed to hash password'. This indicates the crypto call itself blew up, not the input policy.

Source

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

   * @param password - The plaintext password to hash
   * @returns The bcrypt hash
   * @throws PasswordHashError if password is invalid
   */
  async hash(password: string): Promise<string> {
    const validation = this.validate(password);

    if (!validation.isValid) {
      throw new PasswordHashError(
        validation.errors.join('; '),
        'VALIDATION_FAILED'
      );
    }

    try {
      // bcrypt automatically generates a random salt per hash
      return await bcrypt.hash(password, this.config.rounds);
    } catch (error) {
      throw new PasswordHashError(
        'Failed to hash password',
        'HASH_FAILED'
      );
    }
  }

  /**
   * Verifies a password against a bcrypt hash.
   * Uses timing-safe comparison internally.
   *
   * @param password - The plaintext password to verify
   * @param hash - The bcrypt hash to compare against
   * @returns True if password matches, false otherwise
   */
  async verify(password: string, hash: string): Promise<boolean> {
    if (!password || !hash) {
      return false;
    }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Guarantee password is a non-null string before calling hash() (typeof password === 'string').
  2. Log the caught error in the surrounding handler — this wrapper swallows the original message, so capture err.code and rethrow with cause if you control the call site.
  3. Reinstall dependencies (rm -rf node_modules && npm install) if the bcryptjs module itself fails to load or execute.
  4. Verify the hasher instance was built with rounds between 10 and 20.

Example fix

// before
const hash = await hasher.hash(req.body.password); // req.body.password may be null

// after
const raw = req.body?.password;
if (typeof raw !== 'string') throw new PasswordHashError('password must be a string', 'VALIDATION_FAILED');
const hash = await hasher.hash(raw);
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof password !== 'string' || password.length === 0) {
  throw new TypeError('password must be a non-empty string');
}

Type guard

function isHashablePassword(pw: unknown): pw is string {
  return typeof pw === 'string' && pw.length > 0;
}

Try / catch

try {
  return await hasher.hash(password);
} catch (err) {
  if (err instanceof PasswordHashError && err.code === 'HASH_FAILED') {
    logger.error({ err }, 'bcrypt failure'); // wrapper hides cause: log and rethrow as 500
    throw new InternalError('password hashing unavailable');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a non-string password from untyped JS (bcryptjs throws on null/undefined/number); a corrupted or partially installed bcryptjs package after the #1608 bcrypt->bcryptjs swap; rounds misconfigured outside bcryptjs limits (the constructor already clamps to 10-20, so this only happens with a mutated config object).

Common situations: Callers bypassing TypeScript types with null from req.body.password; a stale node_modules after switching dependencies between bcrypt and bcryptjs; CI caching a broken native/JS build of the hash library.

Related errors


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