Skip to content

Commit 539fdba

Browse files
committed
fix(api): classify the take-over method on an unclear outcome too
A passkey prompt (e.g. GitHub's webauthn step) asks for a security key rather than a code, so the outcome classifier can land on 'unknown' instead of 'needs_2fa'. Classify the verification method on 'unknown' as well so the take-over panel still recognizes the passkey and guides the user to a code method. The classifier degrades to 'other' for a genuinely non-verification page, so the panel stays generic there.
1 parent b12f3fe commit 539fdba

2 files changed

Lines changed: 29 additions & 4 deletions

File tree

‎apps/api/src/browserbase/browser-credential-signin.service.spec.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,24 @@ describe('BrowserCredentialSigninService', () => {
193193
expect(result.twoFactorMethod).toBe('passkey');
194194
});
195195

196+
it('classifies a passkey prompt that lands as an unclear outcome (GitHub webauthn)', async () => {
197+
// GitHub's passkey step asks for a key, not a code, so the outcome classifier
198+
// can return 'unknown' — we still read the method so the panel guides the user.
199+
const extract = jest
200+
.fn()
201+
.mockResolvedValueOnce({ state: 'unknown' })
202+
.mockResolvedValueOnce({ state: 'unknown' })
203+
.mockResolvedValueOnce({ method: 'passkey' });
204+
const sessions = makeSessions(extract, jest.fn().mockResolvedValue(undefined));
205+
const profiles = makeProfiles(profile);
206+
withCredentials({ username: 'user@x.com', password: 'secret' });
207+
208+
const result = await run(sessions, profiles);
209+
210+
expect(result.failure).toBe('unknown');
211+
expect(result.twoFactorMethod).toBe('passkey');
212+
});
213+
196214
it('re-points the live view after the sign-in attempt so a 2FA take-over sees the right tab', async () => {
197215
const extract = jest
198216
.fn()

‎apps/api/src/browserbase/browser-credential-signin.service.ts‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,19 @@ export class BrowserCredentialSigninService {
259259
await record(outcome, FAILURE_REASON[outcome]);
260260
finish('warn');
261261

262-
// A human is about to take over a verification step — read HOW the page
263-
// wants them to verify (enter a code, switch off a passkey, or a
264-
// passkey-only login we can't automate) so the UI gives exact guidance.
262+
// A human is about to take over — read HOW the page wants them to verify
263+
// (enter a code, switch off a passkey, or a passkey-only login we can't
264+
// automate) so the UI gives exact guidance. We also classify on 'unknown'
265+
// because a passkey prompt (e.g. GitHub's webauthn step, which asks for a
266+
// key rather than a code) can land there; the classifier degrades to
267+
// 'other' for a genuinely non-verification page, so the panel stays generic.
265268
// Best-effort: undefined just falls back to the generic take-over copy.
266269
let twoFactorMethod: TwoFactorMethod | undefined;
267-
if (outcome === 'needs_2fa' || outcome === 'challenge') {
270+
if (
271+
outcome === 'needs_2fa' ||
272+
outcome === 'challenge' ||
273+
outcome === 'unknown'
274+
) {
268275
twoFactorMethod = await classifyTwoFactorMethod(activeStagehand);
269276
}
270277
return { isLoggedIn: false, failure: outcome, twoFactorMethod };

0 commit comments

Comments
 (0)