ruvnet/ruflo · error · PasswordHashError
VALIDATION_FAILED
VALIDATION_FAILED
Error message
validation.errors.join('; ') What it means
PasswordHasher.hash() runs its configured password policy through validate() before ever calling bcrypt. When the plaintext violates that policy, it throws a PasswordHashError with code VALIDATION_FAILED whose message is the policy failures joined by '; '. Defaults require 8-128 chars, at least one uppercase, one lowercase, and one digit.
Source
Thrown at v3/@claude-flow/security/src/password-hasher.ts:183
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Hashes a password using bcrypt.
*
* @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.View on GitHub (pinned to fa13ee4ad6)
Solutions
- Read the thrown message — it lists every failed rule (e.g. 'Password must contain at least one digit'), so fix the password to satisfy each named rule.
- Call hasher.validate(password) before hash() and return its errors array to the UI instead of relying on the throw.
- If your product policy intentionally differs, construct PasswordHasher with matching config (e.g. { requireDigit: false, minLength: 10 }) instead of mutating defaults after the fact.
- Keep client-side validation derived from the same PasswordHasherConfig so the two never diverge.
Example fix
// before
const hash = await hasher.hash('weak');
// throws PasswordHashError: Password must contain at least one uppercase letter; ...
// after
const check = hasher.validate('WeakPass1');
if (!check.isValid) throw new UserInputError(check.errors);
const hash = await hasher.hash('WeakPass1'); Defensive patterns
Strategy: validation
Validate before calling
const check = hasher.validate(password);
if (!check.isValid) {
return badRequest(check.errors); // surface policy errors to the caller
}
const hash = await hasher.hash(password); Type guard
function isPolicyCompliant(hasher: PasswordHasher, pw: string): boolean {
return hasher.validate(pw).isValid;
} Try / catch
try {
const hash = await hasher.hash(password);
} catch (err) {
if (err instanceof PasswordHashError && err.code === 'VALIDATION_FAILED') {
return res.status(400).json({ errors: err.message.split('; ') });
}
throw err;
} Prevention
- Derive client-side validation from the same PasswordHasherConfig object you pass to the constructor.
- Always run hasher.validate() in request handlers before hash() so users get field-level feedback.
- In tests, use a known-good password like 'TestPass1' that satisfies all default rules.
When it happens
Trigger: await hasher.hash('') (empty), hash('short1A' is fine but hash('short') is not), hash('alllowercase123'), hash('NOLOWERCASE123') — any input failing the minLength/maxLength/requireUppercase/requireLowercase/requireDigit/requireSpecial checks. Typical when requireSpecial was enabled in config but the caller's form does not enforce it.
Common situations: Signup/change-password flows where UI validation is looser than the hasher config; importing legacy users whose passwords predate the policy; tests using passwords like 'test' or 'password'; enabling requireSpecial: true without updating the client-side checker.
Related errors
- INVALID_ROUNDS
- Validation failed: ${result.error}
- ${toolsJson} must contain a JSON array of {name, description
- basePath contains disallowed characters
- memory path contains disallowed characters
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/51abdff840ad0190.
Report an issue: GitHub.