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
- Guarantee password is a non-null string before calling hash() (typeof password === 'string').
- 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.
- Reinstall dependencies (rm -rf node_modules && npm install) if the bcryptjs module itself fails to load or execute.
- 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
- Never call hash() with unvalidated external input; assert string type at the boundary.
- Pin bcryptjs in package.json and lockfile to avoid half-migrated installs after the bcrypt->bcryptjs switch (#1608).
- Treat HASH_FAILED as an infrastructure error (500/log), never as user feedback.
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
- INVALID_ROUNDS
- VALIDATION_FAILED
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
- unknown strategy "${name}". Available: ${roster.map((r) => r
- signBacktestArtifact: privateKey must be 32 bytes (got ${pri
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/7ca569573140fa96.
Report an issue: GitHub.