Spaces:
Running
Running
| # WebAuthn Human-in-the-Loop Attestation Integration Notes | |
| ## Standards References | |
| - **W3C WebAuthn Level 3**: https://www.w3.org/TR/webauthn-3/ | |
| - **FIDO2 CTAP2**: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/ | |
| - **Yubico Signing Pattern**: https://developers.yubico.com/WebAuthn/Concepts/Using_WebAuthn_for_Signing.html | |
| - **COSE Algorithms**: https://www.iana.org/assignments/cose/cose.xhtml | |
| - **SimpleWebAuthn**: https://simplewebauthn.dev/docs/ | |
| ## Architecture | |
| ``` | |
| Cursor/Claude operator interface | |
| │ sensitive action detected | |
| ▼ | |
| mcpWebAuthnApprove() → returns AuthenticationOptions to browser | |
| │ | |
| ▼ | |
| Browser: startAuthentication() from @simplewebauthn/browser | |
| Touch ID / FIDO2 gesture | |
| │ | |
| ▼ | |
| WebAuthn assertion (clientDataJSON + authenticatorData + ECDSA-P256 sig) | |
| │ | |
| ▼ | |
| verifyAssertionAndEmitDSSE() → WebAuthnDSSESignature | |
| │ | |
| ▼ | |
| augmentDSSEWithWebAuthn() → DSSE envelope with 2 signatures: | |
| 1. HMAC-SHA256 (automated, machine identity) | |
| 2. WebAuthn ECDSA-P256 (human, biometric/PIN-bound) | |
| │ | |
| ▼ | |
| augmentReceiptWithWebAuthn() → JSONL receipt + humanAttestation block | |
| │ | |
| ▼ | |
| submitDSSEToRekor() → Rekor entry with dual-signature envelope | |
| ``` | |
| ## MCP Tool Integration | |
| Two new MCP tools exposed to Cursor/Claude users: | |
| ### `webauthn_register` | |
| ```typescript | |
| server.tool("webauthn_register", { | |
| operatorDID: z.string(), | |
| userName: z.string(), | |
| }, async ({ operatorDID, userName }) => { | |
| const opts = mcpWebAuthnRegister(szlRP, operatorDID, userName); | |
| // Return to browser for startRegistration(@simplewebauthn/browser) | |
| return { registrationOptions: opts }; | |
| }); | |
| ``` | |
| ### `webauthn_approve` | |
| ```typescript | |
| server.tool("webauthn_approve", { | |
| operatorDID: z.string(), | |
| receiptId: z.string(), | |
| }, async ({ operatorDID, receiptId }) => { | |
| const receipt = await lookupReceipt(receiptId); | |
| const opts = mcpWebAuthnApprove(szlRP, operatorDID, receipt.envelope.payload); | |
| // Return to browser for startAuthentication() | |
| // After browser completes gesture, call verify_assertion tool: | |
| return { signingOptions: opts, pendingApproval: receiptId }; | |
| }); | |
| ``` | |
| ## Sensitive Action Policy (when to require WebAuthn) | |
| Configure in `a11oy/src/webauthn/policy.ts`: | |
| | Action | Require WebAuthn? | Reason | | |
| |---|---|---| | |
| | `deploy` to production | YES | Irreversible side effect | | |
| | `key_rotation` | YES | Cryptographic identity change | | |
| | `federation_cross_ref` | YES | Cross-org trust boundary | | |
| | `validate` | NO | Automated quality gate | | |
| | `audit_export` | YES | Data sovereignty | | |
| | `deploy` to staging | NO | Reversible test env | | |
| ## STAGED-ADVISORY: Production Dependencies | |
| ```bash | |
| # Server-side (Node.js Pepr/MCP): | |
| npm install @simplewebauthn/server | |
| # Browser-side (Cursor plugin / web UI): | |
| npm install @simplewebauthn/browser | |
| # Replace dev stubs: | |
| # verifyRegistration → verifyRegistrationResponse from @simplewebauthn/server | |
| # verifyAssertionAndEmitDSSE → verifyAuthenticationResponse from @simplewebauthn/server | |
| ``` | |
| ## Security Properties | |
| 1. **Human presence**: UP flag verified — physical authenticator interaction required | |
| 2. **User verification**: UV flag verified — biometric/PIN required, not just tap | |
| 3. **Replay prevention**: signCount monotonically increasing; counter replay rejected | |
| 4. **Payload binding**: challenge = SHA-256(DSSE payload) — assertion is cryptographically | |
| bound to the specific receipt being approved, not just any receipt | |
| 5. **Origin binding**: clientDataJSON.origin checked — prevents cross-site relay attacks | |
| 6. **Additive, not substitutive**: WebAuthn sig appended alongside HMAC; both required | |
| for "dual-control" governance actions | |