Skip to content

fix(extension): do not fill a 2FA code into a form with empty credentials - #331

Merged
vineethkrishnan merged 1 commit into
mainfrom
fix/totp-autofill-gating
Jul 29, 2026
Merged

fix(extension): do not fill a 2FA code into a form with empty credentials#331
vineethkrishnan merged 1 commit into
mainfrom
fix/totp-autofill-gating

Conversation

@vineethkrishnan

Copy link
Copy Markdown
Owner

Follow-up to #328. Fixes a regression that shipped in 1.26.0.

The bug

On a single-step login page the 2FA code could be filled while the username and password were still empty. It looks broken, and it puts a code on screen that cannot be submitted yet.

Two causes:

Ordering. maybeAutofillOtp ran immediately from refreshMatches, while the credential autofill waits 500ms and then polls for its form. Even when both were going to fire, the code arrived first.

Disagreeing conditions. The credential fill requires matches.length === 1; the code fill only required totpMatches().length === 1. On a host with two saved logins where only one carries a TOTP, the credential fill correctly stood down as ambiguous and the code fill went ahead regardless, so the credentials were never filled at all. This is the more likely path to what was reported.

The fix

The standalone pass now stands down whenever a visible login form still has an empty password box. That fill owns the code and writes username, password and code in one pass, which removes the ordering race outright rather than papering over it with a longer delay.

Gating on an empty password rather than on the mere presence of a form keeps a real case working: an SPA that fills credentials in one step and only then reveals its code box still gets filled, because by that point nothing is pending.

The ambiguity rule now matches the credential path exactly: exactly one matching login, and that login carries the secret.

Resulting behaviour

Situation Result
Pick a login from the picker username + password + code together
Autofill on load, single-step form username + password + code together
Autofill on load, 2FA-only second page code filled (nothing else to fill)
Autofill off code only when you click the emblem
Login form with empty credentials no code fill

Tests

The decision moved out of content.ts into a pure predicate in extension/utils/otp-autofill.ts, alongside the existing one-time-code detector and field classifier. The policy was previously trapped inside defineContentScript and could not be tested at all.

Nine cases cover the table above, including the regression itself (hasPendingCredentialForm: true must not fill), which returns true under the old logic and false now.

Verification

  • npm run typecheck clean
  • npm test 74/74 passing (65 before, 9 new)
  • npm run build succeeds

Not exercised against a live 2FA page in a browser.

…ials

The standalone 2FA pass added in the previous release fired independently of the credential fill, so on a single-step login page the code could land while the username and password were still empty. It looks broken, and it burns the user's attention on a code they cannot submit yet.

Two things caused it. The pass ran immediately from refreshMatches while the credential autofill waits 500ms and then polls for its form, so the code arrived first even when both were going to fire. Worse, the two paths disagreed on when a page is unambiguous: the credential fill requires exactly one matching login, while the code fill only required exactly one match CARRYING A SECRET. On a host with two saved logins where one has a TOTP, the credential fill correctly stood down as ambiguous and the code fill went ahead anyway, so the credentials were never filled at all.

The standalone pass now stands down whenever a visible login form still has an empty password box. That fill owns the code and writes username, password and code together, which removes the ordering race entirely rather than papering over it with a delay. It is gated on an empty password rather than on the mere presence of a form so the SPA case still works: a site that fills credentials in one step and only then reveals its code box is filled, because by that point nothing is pending. The ambiguity rule now matches the credential path exactly.

Net behaviour: picking a login from the picker, or autofill-on-load on a single-step form, fills all three together; a 2FA-only second page still fills on its own; a form with empty credentials is left alone; and with autofill off the code is still only ever filled by clicking the emblem.

The decision moves out of the content script into a pure predicate in utils, alongside the existing one-time-code detector and field classifier, so the policy is unit-testable rather than trapped inside defineContentScript. Nine cases cover the table above, including the regression itself.
@vineethkrishnan
vineethkrishnan merged commit 8ca272b into main Jul 29, 2026
7 checks passed
@vineethkrishnan
vineethkrishnan deleted the fix/totp-autofill-gating branch July 29, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant