agentic/code/frameworks/security-engineering/skills/auth-factor-design/SKILL.md
Decision aid for authentication factor architecture — have/know/are mapping, coercion resistance, FIDO2 PIN/UV policy, and PRF hot-path anti-patterns.
npx skillsauth add jmagly/aiwg auth-factor-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Decision aid for authentication factor architecture. Use when designing or reviewing any system whose unlock, login, or operation gating depends on more than one factor — and especially when biometrics, hardware tokens, or "something you know" enter the design.
The skill forces three explicit exercises: a factor inventory, a coercion-resistance matrix, and a lockout/recovery policy. Without all three, the design isn't complete.
sdlc-complete security-architect for protocol selection)| Class | Examples | What "knowing it" means | |---|---|---| | Have | Hardware token, USB drive, smart card, phone with attestation | Possession demonstrates auth | | Know | Password, PIN, passphrase, recovery code, pattern, security questions | Recall demonstrates auth | | Are | Fingerprint, face, voice, gait, behavioral biometrics | Body demonstrates auth |
A "true" multi-factor system requires factors from at least two distinct classes. Three of the same class is not multi-factor — two passwords is one factor twice.
Before declaring an auth design complete, fill out:
| Factor | Class | Specific instance | Required for which operations? | Lost-recovery procedure |
|---|---|---|---|---|
| YubiKey 5 OTP slot | have | hardware HMAC-SHA1 PRF | unlock, secret derivation | revoke at HQ, reissue |
| YubiKey Bio (FIDO2 PRF) | are+have | fingerprint + hmac-secret w/ uv=true | unlock, signing | revoke + reissue (≤4 weeks per fingerprint enrollment) |
| FIDO2 PIN | know | PIN on Bio key | required when uv=true | reset via reset-protocol; bricks key after N failures |
| Recovery passphrase | know | 6-word BIP39 phrase | emergency rotation only | offline backup at HQ |
Required: at least one row from each of two classes for any operation that protects high-value assets. Document operations that bypass this rule with explicit risk acceptance.
For each factor, document whether it can be coerced and what mitigates the coercion.
| Factor | Coercible? | How | Mitigation | |---|---|---|---| | Password / PIN | Yes — operator under duress reveals it | Mitigation: duress code that triggers fail-closed silent lockout | | Hardware token | Yes — physical seizure | Mitigation: time-lock, witness requirement, second-factor at HQ | | Biometric | Always coerced. Operator's body is present; "extracting" the biometric is trivial under duress | Mitigation: pair with a "know" factor (FIDO2 PIN) so coerced biometric alone fails | | Geolocation | Coercible (move operator) | Mitigation: not a real factor; use as risk signal only |
A system with hardware token + biometric and no "know" factor is not coercion-resistant. The attacker compels the operator to come to the device with the token in hand and the finger ready. There is nothing the operator can decline to provide.
A FIDO2 PIN as the third factor — REQUIRED for PRF assertion via uv=true — adds:
Cost: zero hardware. Recommended for any production deployment of FIDO2/WebAuthn PRF.
For hardware-backed authenticated key derivation:
uv: required in the assertion — forces user verification| Use case | Suggested tool | Vetted alternatives |
|---|---|---|
| Production C/C++ | libfido2 (Yubico's reference C lib) | python-fido2 only behind a sandbox; PyHanko/cose for high-level signing |
| FIDO2 CLI | fido2-token, fido2-assert (libfido2 binaries) | OpenSC for smart-card-style use |
| Browser-side WebAuthn | Web Crypto API + navigator.credentials.create/get | Server libs: webauthn4j (JVM), simplewebauthn (Node), python-fido2 (server) |
| Testing | Yubico's fido2-token -L, virtual authenticators (Chrome devtools, soft-fido2) | Hardware test rigs only for pre-production validation |
python-fido2 in the biometric PRF assertion path introduces a wide attack surface for the most sensitive operation in the system:
python-fido2, cryptography, cffi, pyusb — any of these getting a typosquat or supply-chain compromise = PRF interceptionlibfido2 is ~50KB statically linkable, audited, fewer transitive depsRemediation options:
libfido2 C binaries (fido2-token, fido2-assert) directly — they support hmac-secret. Drop Python from the hot path entirely.python-fido2 + transitive deps with hash-locked wheels in your bootstrap; verify per-wheel SHA-256 against an offline manifest; reproducibly build the bootstrap./proc, read-only mounts).rp.id in WebAuthn / FIDO2 is canonically a domain name. Some authenticators behave oddly with non-domain RP IDs (e.g., "agent-box"). For non-web contexts:
agent-box.example.com)| Factor | Failure response | |---|---| | FIDO2 PIN | Hard lockout after N failures (default ~8); brick key on M (default 3 lockout cycles); requires reset protocol with PIN+attestation | | Biometric on hardware token | Fall back to PIN (if PIN set); on N failures of bio, force PIN; never "skip bio" | | Application password | Rate-limit + account lock with side-channel notification; CAPTCHA after 3 failures (anti-bot, not anti-human) | | Recovery code (single-use) | Mark used immediately; rotate active set; alert on use |
For each authenticator in your system, document:
Factor: FIDO2 PIN on Bio key
Wrong-PIN behavior:
After 1: warning + 1-second delay
After 3: 5-second delay
After 5: 30-second delay
After 8: full lockout, requires reset
Lockout cycles before key brick: 3 (default)
Reset procedure:
fido2-token -R (factory reset, wipes credentials)
Re-enroll fingerprint, re-set PIN
Re-register with all RPs (operationally expensive — design for this)
If your design doesn't have this written down, it's incomplete.
When the threat model includes coercion (high-value targets, journalists, security operators in hostile environments), build in:
| Countermeasure | How | |---|---| | Duress PIN | Separate PIN that triggers visible-success but secret wipe. Hardware support varies; some FIDO2 firmware supports it, most don't. Document if your hardware does. | | Time-lock | Operations require N-minute delay; operator can secretly trigger a longer delay during which silent alarm fires | | Witness requirement | High-value operations require a second operator present (cryptographically verified — e.g., second YubiKey assertion) | | Geographic / device fence | Operations only succeed from authorized locations or hardware; revocable centrally | | Silent canary / heartbeat | Operator periodically demonstrates non-coercion; missed heartbeat triggers alert (review M7: heartbeat is killable by root, mitigate via signed live image) |
Important: every coercion countermeasure has operational cost. A duress PIN that the operator forgets locks them out as effectively as the attacker does. Test the failure modes.
Original design: 2-of-2 with one biometric (YubiKey 5 + YubiKey Bio with fingerprint).
What this skill flags:
Remediation:
uv=true in PRF assertionpython-fido2 in PRF hot pathOriginal design: Python program calls python-fido2 to do the biometric PRF assertion.
What this skill flags:
Remediation:
libfido2 C tools (fido2-token, fido2-assert -h hmac-secret)Original design: uv: REQUIRED documented, but lockout/fallback behavior not specified.
What this skill flags:
Remediation:
fido2-token -L against a test key, document observed behavior under N failuresfactor-design-rationale.md per-factorWhen this skill is invoked as part of a review, produce a factor-design-rationale.md (template in templates/factor-design-rationale.md) with:
If issues are found, produce findings in the standard format:
### Finding: <SHORT-NAME>
**Severity**: BLOCK | HIGH | MEDIUM | LOW
**Section**: auth-factor-design <section reference>
**Issue**: <one paragraph>
**Remediation**: <concrete fix>
**References**: <NIST SP 800-63B, FIDO2 spec, etc.>
physical-threat-modeling (Tier 2) — coercion is in its threat librarydegraded-mode-design (Tier 2) — what happens when factors failchain-of-trust-design — the factors only matter if the verifier is trustedfactor-design-rationale.mddata-ai
Report which research-corpus radar sidecars are overdue for refresh. Computes staleness (days since last refresh vs the cadence window) for every radar, sorted most-overdue-first. Runs via `aiwg corpus radar-status`.
data-ai
Aggregate research-corpus radar sidecars into a corpus or per-cluster freshness report — totals, overdue count, per-cluster / per-GRADE / per-trajectory breakdowns, an overdue table, and per-radar rationale snippets. Runs via `aiwg corpus radar-report`.
testing
Scaffold radar/freshness sidecars for research-corpus REFs. Pulls title/authors from the citation sidecar and GRADE from the analysis doc, defaults the refresh cadence from GRADE and the cluster from a corpus-local map, and stamps documentation/radar/REF-XXX-radar.md. Runs via `aiwg corpus radar-init`.
data-ai
Compute an entity's publication trajectory — per-year paper counts, topic drift, hot-streak detection (≥3 consecutive A-grade years), and career phase. Runs via `aiwg corpus profile-temporal`.