Files
suaveplan/packages/security-stack/security/password-strength/src/conformance.test.ts
2026-05-27 23:47:53 +01:00

98 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import { assess } from "./assess/assess.js";
import { dictionaryMatch } from "./matchers/dictionary/dictionary.js";
import type { Match } from "./matchers/types.js";
import { estimateGuesses } from "./scoring/estimate-guesses.js";
/**
* Wheeler 2016 corpus agreement tests. We do not vendor the upstream test
* vector JSON (GPLv2 half of the dual licence); instead we assemble a curated
* set of canonical inputs and verify the per-pattern guess estimates agree
* within ≤1 bit log10 against the closed-form formulae documented in
* Wheeler §4.
*/
describe("Wheeler corpus agrees to <=1 bit log10", () => {
const dicts = { passwords: new Map([["password", 1]]) };
const cases: { match: Match; password: string; expectedLog10: number }[] = [
{
match: {
pattern: "dictionary",
i: 0,
j: 7,
token: "password",
matchedWord: "password",
rank: 1,
dictionaryName: "passwords",
reversed: false,
l33t: false,
},
password: "password",
expectedLog10: 0, // log10(1) = 0
},
{
match: {
pattern: "sequence",
i: 0,
j: 3,
token: "abcd",
sequenceName: "lower",
sequenceSpace: 26,
ascending: true,
},
password: "abcd",
// 4 (base) * 4 (length) * 1 = 16 → log10 ≈ 1.20
expectedLog10: 1.2,
},
{
match: {
pattern: "date",
i: 0,
j: 9,
token: "11/22/1985",
year: 1985,
month: 11,
day: 22,
separator: "/",
},
password: "11/22/1985",
// 365 * 41 * 4 = 59860 → log10 ≈ 4.78
expectedLog10: 4.78,
},
{
match: {
pattern: "regex",
i: 0,
j: 3,
token: "1985",
regexName: "recent_year",
regexMatch: ["1985"] as unknown as RegExpMatchArray,
},
password: "1985",
// |1985 - 2026| = 41 → log10 ≈ 1.61
expectedLog10: 1.61,
},
];
for (const c of cases) {
it(`${c.match.pattern} / ${c.match.token}`, { timeout: 5000 }, () => {
const g = estimateGuesses(c.match, c.password, {
referenceYear: 2026,
});
const log10 = Math.log10(g);
expect(Math.abs(log10 - c.expectedLog10)).toBeLessThanOrEqual(1);
});
}
it("full assess agrees with corpus reference within bucket", {
timeout: 5000,
}, () => {
const r = assess("password", {
dictionaries: { passwords: dicts.passwords },
});
expect(r.score).toBe(0);
const matches = dictionaryMatch("password", dicts);
expect(matches.length).toBeGreaterThanOrEqual(1);
});
});