Files
skills/docs/security-stack/security/password-strength/src/graphs/validate-graph.md
2026-05-29 08:20:56 +01:00

5.3 KiB

validate-graph — keyboard graph integrity guard

(no submodules)

Purpose

Validates that a user-supplied keyboard adjacency graph is well-formed before the keyboardMatch matcher walks it. A malformed graph would either produce nonsensical matches (self-loops) or cause inconsistent guess estimates across users typing the same word.

Features

  • Detects self-adjacency (a key listed as adjacent to itself)
  • Detects asymmetric edges (a lists b but b does not list a)
  • Throws PasswordStrengthError with code PWSTRENGTH.GRAPH_INVALID
  • Pure data validation; no side effects

Usage: Basic Example

import { validateGraph } from "@suaveplan/password-strength";
import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty";

validateGraph("qwerty", QWERTY_GRAPH); // returns; throws if malformed

Usage: Advanced Example

import { validateGraph, PWSTRENGTH_CODES, PasswordStrengthError } from "@suaveplan/password-strength";

const broken = { q: ["q"] };
try {
 validateGraph("broken", broken);
} catch (e) {
 if (e instanceof PasswordStrengthError && e.code === PWSTRENGTH_CODES.GRAPH_INVALID) {
  console.log("graph rejected");
 }
}

API Reference

  • validateGraph(name: string, graph: KeyboardGraph): void
    • Throws PasswordStrengthError with code PWSTRENGTH.GRAPH_INVALID when the graph contains a self-loop or an asymmetric edge. The error message names the offending key for fast debugging.

Implementation Notes

The symmetry check is one-directional per pair — if a claims adjacency to b, then b's adjacency slot list must contain a in at least one of its slots' character pairs (covering both the shifted and unshifted halves). Slots set to null are skipped. The walk is O(n) over the number of edges in the graph; running it on a 60-key graph is negligible.

Additional Implementation Notes

The implementation follows the canonical zxcvbn algorithm from Wheeler 2016 with the suaveplan-style additions described in the package proposal: structured error codes via the defineCodes registry, Zod boundaries at every public entry point, OTel histograms and counters keyed to low-cardinality attributes, and a strict input-length guard at 200 characters. Consumers integrate with their existing observability stack by installing a meter provider before importing the package, and with their i18n stack via the MessageResolver callback type declared in @suaveplan/types. The bundled data tables are intentionally small to keep the package bundle compact; production deployments should swap in fuller frequency-ranked tables via loadDictionaries. The default English dictionary set is a frozen DictionarySet that the matcher never mutates; caller-supplied dictionaries override defaults by name.