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

2.8 KiB

qwerty — QWERTY keyboard adjacency graph

(no submodules)

Purpose

Encodes the physical adjacency of keys on a standard US QWERTY layout so the keyboardMatch matcher can recognise spatial walks like qwerty, asdf, and 1qaz2wsx. The graph drives the keyboard-walk guess estimate in Wheeler 2016 section 4.5. The encoding mirrors the upstream zxcvbn reference implementation's adjacency_graphs.coffee.

Features

  • Complete coverage of the alphanumeric, symbol, and shifted-symbol rows
  • Adjacency slots in zxcvbn's canonical order (left, top-left, top-right, right, bottom-right, bottom-left)
  • Each slot encodes both the unshifted and shifted character at that key
  • Frozen at module load — safe against accidental mutation

Usage: Basic Example

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

console.log(QWERTY_GRAPH.q); // [null, "1!", "2@", "wW", "aA", null]

Usage: Advanced Example

import { keyboardMatch } from "@suaveplan/password-strength/matchers/keyboard";
import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty";
import { DVORAK_GRAPH } from "@suaveplan/password-strength/graphs/dvorak";

const matches = keyboardMatch("qwerty", {
 graphs: { qwerty: QWERTY_GRAPH, dvorak: DVORAK_GRAPH },
});
console.log(matches);

API Reference

  • QWERTY_GRAPH: KeyboardGraph — frozen Record<string, ReadonlyArray<string | null>> mapping each key to its six adjacency slots.

Implementation Notes

The matcher checks both characters of each slot when classifying a key press: matching against the second character flags the press as shift-required (shiftedCount += 1). Slots that point off the edge of the keyboard are null. The graph deliberately preserves the upstream slot order so callers porting from zxcvbn consume the same data layout.

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.