Describe the bug
On the login form rendered by Tutor (#tutor-login-form), when the browser has autofilled the password field, the first keystroke the user types is replaced by the character 0 — regardless of which key is pressed.
Typing 123 over an autofilled password produces 023. Typing abc produces 0bc. It is always the first keystroke, and always 0.
The password field is not visible by default, so users cannot see this happening. They only discover it when the login fails.
Steps to reproduce
- Visit any page that renders the Tutor login form (e.g. a lesson page while logged out, or
/dashboard/).
- Let Chrome autofill saved credentials for the site (native Chrome password manager, no extensions involved).
- Click into the password field and select all.
- Type any single character.
- Observe the field now contains
0 instead of the character typed.
Expected behavior
The typed character appears in the field.
Actual behavior
The first character is replaced by 0.
Root cause evidence
I instrumented the field's value setter and captured a stack trace on the offending write. Exactly one write occurs on that keystroke, and it comes from Alpine's bind path inside tutor-core.js:
at HTMLInputElement.set (instrumented setter)
at rv (https://<site>/wp-content/plugins/tutor/assets/js/tutor-core.js?ver=4.0.7:166:741)
at rp (https://<site>/wp-content/plugins/tutor/assets/js/tutor-core.js?ver=4.0.7:166:135)
The captured write had length: 1 and value 0.
In the minified bundle, rv contains this branch:
function rv(e, t) {
if (rI(e)) { ... }
else if (rR(e)) {
if (Number.isInteger(t)) { e.value = t } // <-- this branch
else if (!Array.isArray(t) && typeof t !== "boolean" && ![null, void 0].includes(t)) { e.value = String(t) }
else { ... e.checked ... }
}
...
}
The password input reaches a branch intended for checkbox/radio inputs, and a numeric coercion writes an integer into a text/password field. No other code on the page writes to this input — I verified that by spying on every write to .value during the keystroke.
The markup involved
Rendered by Tutor's login form template:
<div class="tutor-input-field tutor-mb-8" x-data="{ show: false, value: '' }">
<input :type="show ? 'text' : 'password'"
class="tutor-form-control tutor-input"
placeholder="Password"
name="pwd"
x-model="value"
size="20" required
autocomplete="off" />
</div>
Two things about this markup look relevant:
x-model="value" on a field the browser fills means Alpine's internal state and the DOM value can diverge — Chrome's autofill does not always go through the events Alpine listens to.
- The eye toggle is gated on
x-show="value.length > 0", i.e. on Alpine's state rather than the field's actual value. When the state is out of sync, the show-password control never appears, which is what hides this bug from users.
Environment
- Tutor LMS 4.0.7 (latest at time of writing;
wp plugin list reports no update available)
- Tutor LMS Pro 4.0.7
- Alpine 3.15.2 (as bundled in
tutor-core.js)
- WordPress 6.9.4, PHP 8.2.30
- Chrome, native password manager, no password-manager extensions
- Theme: Avada + Fusion Builder
Caveat, stated honestly: this was diagnosed on a production site that also runs WPML, Yoast, Autoptimize and SiteGround's optimizer. I have not reproduced it on a clean install. The stack trace points inside tutor-core.js with no other writer to the field, so I do not believe those plugins are involved — but I can't rule it out, and I'm happy to test a patch or gather more data if that helps.
Suggested direction
Whatever the exact branch, a text or password input should never receive a numeric coercion of its bound value. Guarding the Number.isInteger branch on the input actually being a checkbox/radio would prevent this class of bug.
Separately, gating the show-password control on value.length makes the field silently unusable whenever Alpine's state and the DOM diverge — reading the input's own value there would be more robust.
Describe the bug
On the login form rendered by Tutor (
#tutor-login-form), when the browser has autofilled the password field, the first keystroke the user types is replaced by the character0— regardless of which key is pressed.Typing
123over an autofilled password produces023. Typingabcproduces0bc. It is always the first keystroke, and always0.The password field is not visible by default, so users cannot see this happening. They only discover it when the login fails.
Steps to reproduce
/dashboard/).0instead of the character typed.Expected behavior
The typed character appears in the field.
Actual behavior
The first character is replaced by
0.Root cause evidence
I instrumented the field's
valuesetter and captured a stack trace on the offending write. Exactly one write occurs on that keystroke, and it comes from Alpine's bind path insidetutor-core.js:The captured write had
length: 1and value0.In the minified bundle,
rvcontains this branch:The password input reaches a branch intended for checkbox/radio inputs, and a numeric coercion writes an integer into a text/password field. No other code on the page writes to this input — I verified that by spying on every write to
.valueduring the keystroke.The markup involved
Rendered by Tutor's login form template:
Two things about this markup look relevant:
x-model="value"on a field the browser fills means Alpine's internal state and the DOM value can diverge — Chrome's autofill does not always go through the events Alpine listens to.x-show="value.length > 0", i.e. on Alpine's state rather than the field's actual value. When the state is out of sync, the show-password control never appears, which is what hides this bug from users.Environment
wp plugin listreports no update available)tutor-core.js)Caveat, stated honestly: this was diagnosed on a production site that also runs WPML, Yoast, Autoptimize and SiteGround's optimizer. I have not reproduced it on a clean install. The stack trace points inside
tutor-core.jswith no other writer to the field, so I do not believe those plugins are involved — but I can't rule it out, and I'm happy to test a patch or gather more data if that helps.Suggested direction
Whatever the exact branch, a text or password input should never receive a numeric coercion of its bound value. Guarding the
Number.isIntegerbranch on the input actually being a checkbox/radio would prevent this class of bug.Separately, gating the show-password control on
value.lengthmakes the field silently unusable whenever Alpine's state and the DOM diverge — reading the input's own value there would be more robust.