4

If I inspect an <input type="password"/> from my Tampermonkey script and observe changes using a change handler for a password field that is filled by the Password Manager feature of Chrome, the value remains empty until I physically perform a real click in the page. I have tried in my script clicking in the page, but Chrome knows it’s not a real click. When I physically click in the page, the change event suddenly fires, the password field gets a value, and then my script reacts properly (due to having the change event). But I wrote this script to avoid having to interact with the page in the first place, so this defeats the point of my script.

Is there a way to get TamperMonkey to mark the page as having had the user interact with it (e.g., some hypothetical GM_setUserTouched() or GM_autoFillPasswords() API) so that the Password Manager feature actually fills the <input type="password"/> in without requiring me to click in the page?

Background

In Chrome, this behavior is documented in #352527 comment 15 where it is unexpected behavior to the reporter and #398805 where there is a case that Chrome fails to implement the behavior I don’t want. It is considered a feature that when autofill and Chrome’s built-in Password Manager fill out a form, password characters are displayed to the user in the password field, but the DOM HTMLInputElement.value is set to "". When the user interacts with the page, such as by clicking in it or pressing a key, Chrome modifies HTMLInputElement.value to contain the password and a change event is fired at the element. The cited reason for doing this is “security reasons” (e.g., if a website script was reading from the password element, it only would have a chance to do so if the user was looking at the page… so popunders or non-visible frames wouldn’t be able to do it or something? I’m not sure what this protects you from: once the user interacts with the page, all of the scripts would have access to the password anyway. And if bad scripts are being served from the same origin as the <input type="password"/>, the website itself has a security flaw, not Chrome…).

Greasemonkey historically has helper APIs and a @grant system to enable the userscript to work around issues like this. Edit: when creating the repro (below), I discovered that Firefox makes the autofill password available to the DOM without waiting for the user to interact with the window. Thus, Greasemonkey doesn’t need a GM_forceAutofill() API because Firefox doesn’t exhibit this Chrome quirk. As a result, Tampermonkey doesn’t have such an API.

Repro

Because people do not believe me when I describe the behavior exhibited by Chrome, I have prepared a repro. It takes some work to get Chrome into a state where it doesn’t think the user has interacted with the page yet, but you should be able to see what I see using these steps:

  1. Open Chrome. I am using Chrome 61.0.3163.91 64-bit on Windows 10.
  2. Navigate to https://fiddle.jshell.net/xqfynp3e/22/show/light/
  3. Enter some bogus username and password and hit enter or click the button. Chrome should prompt you to save the password.
  4. Save the password.
  5. Open Developer tools.
  6. Enter this into Console (to navigate to the page without accidentally interacting with it): window.location.href = 'https://fiddle.jshell.net/xqfynp3e/22/show/light/?1'
  7. Run document.querySelector('input[type=password]').value in Console.
  8. Observe that the form’s password appears to be filled in visually and yet reading the DOM element in Console yields "".
  9. Click in the document.
  10. Run document.querySelector('input[type=password]').value in Console again.
  11. Observe that the form’s password hasn’t changed appearance and yet reading the DOM element in Console yields the bogus password you saved.

My question, restated: how can I get Tampermonkey to perform the “Click in the document” step? How do I tell Chrome’s password auto-filler that I interacted with the page without actually physically interacting with the page?

EDIT: I have found an alternative way to securely store passwords in Chrome and access them via userscripts by using the Credentials Web API’s silent mediation support: https://i.stack.imgur.com/udkWc.jpg

binki
  • 7,754
  • 5
  • 64
  • 110
  • The very purpose of `isTrusted` event property set by the browser on real interactions is to avoid scripts being able to fake that. – wOxxOm Sep 17 '17 at 21:12
  • @BrockAdams Yes, I am sure. Chrome displays the password field **as if** there is a value in it to the user, but when you inspect it in Console, it is empty. Upon “interacting” with the page, `change` events are fired and the DOM suddenly has the value in it. It’s documented on Chrome’s issue tracker as a “security feature”. I updated my question with background – binki Sep 18 '17 at 17:50
  • 1
    @BrockAdams Just because you can install more and more extensions into Chrome such as a password manager doesn’t mean I want to. Yes, Chrome itself lacks an auto-sign-in feature which is why I want to implement a simple stupid one with userscripts. Also, Chrome’s Password Manager syncs with your Google account, your Android device, your Chrome mobile app, etc. It works so well that I’ve never felt the need to spend time investigating a third party thing… – binki Sep 18 '17 at 17:52
  • 1
    @wOxxOm I’m not looking for a way to send real events to the page, necessarily. Just an API to tell Chrome “hey, a user interacted with this page in such a way as to trigger autofilling passwords”. If faking real key/mouse events were an option, it might be a viable option if it triggered Chrome to autofill the password. – binki Sep 18 '17 at 17:53
  • @BrockAdams Please try the repro I provided where I demonstrate that Chrome does not autofill the password until “interacting” with the page. You can easily examine the page I use for the repro to see that there is no condition in the page itself preventing the password from being displayed. And the two screenshots might be enough to convince you of the fact that Chrome will **visually display** a prefilled password without actually prefilling it in the JavaScript view of the DOM. – binki Sep 19 '17 at 03:39

1 Answers1

4

You cannot do this using Google's built-in password storage because, as you stated yourself, Chrome requires user interaction to enable such passwords -- as a security feature.

Specifically, Chrome requires an event with the isTrusted property set to true. Tampermonkey cannot work around this because even Chrome extensions are not able to set the isTrusted property.

See also, this related feature request from 2015.

One solution is to use a password manager that fills these fields without using Google's built-in storage.
There are many available, with varying degrees of cross-device, and cross-browser, support.

Or you can write your own Tampermonkey script to fill in these fields, irregardless of what Chrome has stored.

If you do write a Tampermonkey script, I recommend that you use a secure storage framework, don't hardcode login info into the script.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295