Skip to content

Repository files navigation

Summary

Operator precedence bug in passwordValidator causes password length validation to always pass, ignoring configured passwordMaxLength setting.

Meteor Version

  • Affected version: 3.2.2+
  • Last working version: unknown

Operating System

macOS (reproducible on any OS)

Expected Behavior

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 false

Actual Behavior

All password lengths pass validation regardless of the configured passwordMaxLength or default value.

Match.test("A".repeat(10000), passwordValidator)  // returns true (should be false!)

Root Cause

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) || 256

NOT the intended:

str.length <= (Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)

When passwordMaxLength is undefined:

  • str.length <= undefined evaluates to false
  • false || 256 evaluates to 256 (truthy)
  • Validation passes for ANY length

When passwordMaxLength is set but password exceeds it:

  • str.length <= passwordMaxLength evaluates to false
  • false || 256 evaluates to 256 (truthy)
  • Validation still passes

Reproduction

Steps to reproduce:

  1. Create a new Meteor app with accounts-password
  2. 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
  1. Observe that both tests return true when they should return false

Suggested Fix

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')
  }
);

Security Implications

  • DoS potential: Extremely long passwords can be sent to the server, consuming resources during SHA256 hashing
  • Policy bypass: Any configured passwordMaxLength setting is completely ignored

Related

  • Searched existing issues, no duplicates found

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages