Free security template

Password policy checker

Length, breached-list and repeat checks in one function, with tests for the policy you agree.

It opens with this template loaded and runs in your browser. You don't need an account to use it; a free account saves your work and exports it.

The job it does

When you set or review a sign-up rule, or need to show auditors exactly what the policy enforces. It comes with its own checks, so after you change it you can see at a glance that it still does the job.

What's inside

This is the template exactly as the Security Lab opens it, read-only. Open it to run it, change it and export it.

  • 2 files
  • Runs from policy.js
  • JavaScript
The brief: Password policy checker

When you set or review a sign-up rule, write it as code with tests, so everyone (and the auditors) can see exactly what it enforces. This one follows NIST SP 800-63B: at least 12 characters, checked against breached passwords, no e-mail name, and no composition rules or forced expiry.

Change POLICY and the rules to the policy you agree; each test is one sentence of the policy.

Done when
  • All my tests pass
  • checkPassword("password1234") returns {"ok": false, "problems": ["This passwo…
  • No uncaught errors
policy.js
// Password policy checker, in line with NIST SP 800-63B: long passwords, checked against breached ones,
// no composition rules ("one capital, one symbol") and no forced expiry. Returns every problem, so the
// sign-up form can show them all at once.
export const POLICY = { minLength: 12, maxLength: 128 };

// In production this is a breached-password service (k-anonymity range query); here a small sample.
export const BREACHED = new Set(['password1234', 'qwertyuiop12', '123456789012', 'mangrove2026', 'iloveyou1234']);

export function checkPassword(password, { email = '', breached = BREACHED } = {}) {
  const problems = [];
  const pw = String(password || '');
  const chars = Array.from(pw);
  if (chars.length < POLICY.minLength) problems.push('Use at least ' + POLICY.minLength + ' characters.');
  if (chars.length > POLICY.maxLength) problems.push('Use at most ' + POLICY.maxLength + ' characters.');
  if (breached.has(pw.toLowerCase())) problems.push('This password has appeared in a data breach. Choose another.');
  const name = email.split('@')[0].toLowerCase();
  if (name.length >= 4 && pw.toLowerCase().includes(name)) problems.push('Do not use your e-mail name in your password.');
  if (chars.length && new Set(chars).size <= 2) problems.push('Use more than one or two different characters.');
  return { ok: problems.length === 0, problems };
}

console.log(checkPassword('correct horse battery staple'));
console.log(checkPassword('mangrove2026'));
policy.test.js
import { checkPassword } from './policy';

test('a long passphrase passes', () => {
  expect(checkPassword('correct horse battery staple')).toEqual({ ok: true, problems: [] });
});
test('short passwords fail, whatever they contain', () => {
  expect(checkPassword('Tr0ub4dor&3').ok).toBe(false);
});
test('breached passwords fail, in any case', () => {
  expect(checkPassword('Mangrove2026').problems).toContain('This password has appeared in a data breach. Choose another.');
});
test('the e-mail name is refused', () => {
  expect(checkPassword('nadia.rahman-shopping', { email: 'nadia.rahman@example.com' }).ok).toBe(false);
});
test('no composition rules: lower-case words are fine', () => {
  expect(checkPassword('blue mango river lamp').ok).toBe(true);
});
test('every problem is reported at once', () => {
  expect(checkPassword('aaaa').problems).toHaveLength(2);
});

The lesson behind it

Learn why it works, with a podcast and a quiz for each lesson.

  • Log triage

    Parse auth log lines, group failures by account and address, and flag brute force and spraying.

  • Threat model checklist

    STRIDE questions for one feature as data, with tests that every threat has an owner and a mitigation.

All security templates · Every template