Files
Mankhong Garden ada7963246 feat: add configure_webauthn tool for virtual authenticators (#3276)
Closes #1004.

Adds one opt-in tool, `configure_webauthn`, behind a new
`--categoryWebauthn` flag (off by default), so agents can test passkey
and security key flows with a virtual authenticator. It goes through the
`WebAuthnModel` exported from devtools-frontend in
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/8606246,
which is included in the current `third_party/devtools-frontend` roll.

**Depends on**
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/8629668
(landed 2026-10-22, not rolled yet), which makes `WebAuthnModel` throw
when a CDP command fails. Until that CL lands and is rolled into
`third_party/devtools-frontend`, the model still ignores CDP errors, so
the error and reset paths below only run in the unit tests. This PR
should land after that roll.

### Shape

One authenticator per page, all params top-level primitives so the tool
also works from the CLI (reworked from an `authenticators: [...]` list
during review):

```
configure_webauthn({
  enabled,               // false disables the virtual authenticator environment for the page; other params are ignored
  protocol?,             // 'ctap2' | 'u2f', default ctap2
  transport?,            // 'internal' | 'usb' | 'nfc' | 'ble' | 'hybrid', default internal
  hasResidentKey?,       // default true; false for u2f
  hasUserVerification?,  // default true; false for u2f
  isUserVerified?,       // default true; false tests a failed verification
})
```

- On a later call, omitted params keep the current values.
- Changing only `isUserVerified` updates the authenticator in place, so
passkeys the page registered survive (e.g. register, then make
verification fail and check that sign-in is rejected).
- Changing `protocol`, `transport`, `hasResidentKey` or
`hasUserVerification` replaces the authenticator, since CDP can't change
those on an existing one.
- `enabled: false` always disables the environment for the whole page,
which also removes authenticators other clients (such as the DevTools
WebAuthn panel) added.
- u2f supports neither resident keys nor user verification, so both are
`false` for u2f, also when a later call switches from ctap2 with them
omitted. An explicit `true` with u2f is rejected before any CDP call.
- The tool description asks the agent to call it before the page calls
`navigator.credentials`: a request made without a virtual authenticator
stays pending, and later requests fail with `OperationError: A request
is already pending` until the page navigates.
- When a CDP command fails, the tool throws `Failed to configure
WebAuthn for page <id>: <CDP message>`, drops its stored authenticator,
removes it from the browser on a best-effort basis, and marks the
environment as disabled, so the next call starts from scratch instead of
reusing a stale authenticator id.
- The response shows the authenticator and its stored credentials
(`credentialId` as base64url without padding, matching
`PublicKeyCredential.id`, `rpId`, `discoverable`, `signCount`,
`userName`), formatted by `WebAuthnFormatter` and included in structured
content. Private keys are never printed.
- Page listings (`list_pages` and the other tools that print the page
list) show `webauthn=<protocol>/<transport>` for pages that have one
(`webauthn` in structured content).
- The authenticator is scoped to the page and goes away when it closes.
The tool uses `concurrency: 'page'`.
- Importing credentials with a known private key is left out for now; it
could come back as a follow-up.

### Testing

- `tests/McpPage.test.ts`: `configureWebAuthn()` against a mocked
`WebAuthnModel`: create with defaults, update `isUserVerified` in place
and keep omitted options, replace on a creation option change, disable
(also when another client enabled the environment), list stored
credentials, u2f with both options false after a ctap2 authenticator, an
explicit `true` with u2f rejected without a CDP call, no model, and a
failing call for each CDP command (enable, add, remove, disable,
`setUserVerified`, `getCredentials`), including that the next call
creates a new authenticator.
- `tests/tools/webauthn.test.ts`: the handler passes params through and
attaches the result, plus the only real-browser run: register and sign
in through (the response lists the ID the page got)
`navigator.credentials.create()` / `get()`, the `list_pages` label, a
rejected sign-in after `isUserVerified: false`, the passkey gone after a
`transport` change, and disabling.
- `tests/formatters/WebAuthnFormatter.test.ts`: snapshot tests for
`toString` / `toJSON`, and a test that credential IDs print as
base64url.
- `McpPage`, `webauthn` and `WebAuthnFormatter` test files pass locally;
`npm run gen`, eslint and prettier produce no further changes.
2026-10-23 06:08:34 +00:00
..