Operator precedence bug in passwordValidator causes password length validation to always pass, ignoring configured passwordMaxLength setting.
- Affected version: 3.2.2+
- Last working version: unknown
macOS (reproducible on any OS)
When Meteor.settings.packages.accounts.passwordMaxLength is configured (or defaulting to 256), passwords exceeding this length should be rejected by the validator.
// With default settings (no passwordMaxLength configured)
Match.test("A".repeat(256), passwordValidator) // should be true
Match.test("A".repeat(257), passwordValidator) // should be false
// With passwordMaxLength = 100
Match.test("A".repeat(100), passwordValidator) // should be true
Match.test("A".repeat(101), passwordValidator) // should be falseAll password lengths pass validation regardless of the configured passwordMaxLength or default value.
Match.test("A".repeat(10000), passwordValidator) // returns true (should be false!)In packages/accounts-password/password_server.js, the passwordValidator has an operator precedence bug:
const passwordValidator = Match.OneOf(
Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256),
// ...
);Due to JavaScript operator precedence, <= binds tighter than ||, so this parses as:
(str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength) || 256NOT the intended:
str.length <= (Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)When passwordMaxLength is undefined:
str.length <= undefinedevaluates tofalsefalse || 256evaluates to256(truthy)- Validation passes for ANY length
When passwordMaxLength is set but password exceeds it:
str.length <= passwordMaxLengthevaluates tofalsefalse || 256evaluates to256(truthy)- Validation still passes
Steps to reproduce:
- Create a new Meteor app with
accounts-password - Add the following test code:
import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
// Recreate the buggy validator
const passwordValidator = Match.OneOf(
Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256),
{
digest: Match.Where(str => Match.test(str, String) && str.length === 64),
algorithm: Match.OneOf('sha-256')
}
);
// Test 1: No passwordMaxLength configured - 10000 char password should fail
console.log('10000 chars (no config):', Match.test("A".repeat(10000), passwordValidator));
// Expected: false, Actual: true
// Test 2: Set passwordMaxLength to 100
Meteor.settings = { packages: { accounts: { passwordMaxLength: 100 } } };
console.log('500 chars (max=100):', Match.test("A".repeat(500), passwordValidator));
// Expected: false, Actual: true- Observe that both tests return
truewhen they should returnfalse
Add parentheses to ensure correct operator precedence:
const passwordValidator = Match.OneOf(
Match.Where(str => Match.test(str, String) && str.length <= (Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)),
{
digest: Match.Where(str => Match.test(str, String) && str.length === 64),
algorithm: Match.OneOf('sha-256')
}
);- DoS potential: Extremely long passwords can be sent to the server, consuming resources during SHA256 hashing
- Policy bypass: Any configured
passwordMaxLengthsetting is completely ignored
- Searched existing issues, no duplicates found