Add global autofill guard for hub, env, and transfer inputs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 全局防浏览器自动填充登录账号/密码进业务输入框.
|
||||
* 跳过真正的登录/改密字段;对划转数量等易中招框用 readonly 到聚焦.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var GUARD_ATTRS = {
|
||||
autocomplete: "off",
|
||||
autocorrect: "off",
|
||||
autocapitalize: "off",
|
||||
spellcheck: "false",
|
||||
"data-lpignore": "true",
|
||||
"data-1p-ignore": "true",
|
||||
"data-bwignore": "true",
|
||||
"data-form-type": "other",
|
||||
};
|
||||
|
||||
function looksLikeUsername(v) {
|
||||
return /^[a-z][a-z0-9._-]{1,31}$/i.test(String(v || "").trim());
|
||||
}
|
||||
|
||||
function isAuthField(el) {
|
||||
if (!el || !el.getAttribute) return true;
|
||||
var t = String(el.type || "").toLowerCase();
|
||||
if (t === "hidden" || t === "checkbox" || t === "radio" || t === "file" || t === "submit" || t === "button") {
|
||||
return true;
|
||||
}
|
||||
if (el.getAttribute("aria-hidden") === "true") return true;
|
||||
if (el.tabIndex === -1 && String(el.getAttribute("autocomplete") || "").toLowerCase() === "username") {
|
||||
return true; // 诱饵账号框
|
||||
}
|
||||
var idName = String(el.id || "") + " " + String(el.name || "");
|
||||
if (/^(pwd-|hub-pwd-|login-)/i.test(String(el.id || ""))) return true;
|
||||
if (el.closest) {
|
||||
if (el.closest(".login-form, #login-form, form.login-form, .password-settings, [data-password-settings]")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// env API Key 等 type=password 仍要防登录密码灌入,不在此跳过
|
||||
if (t === "password" && /^(username|password)$/i.test(String(el.name || ""))) {
|
||||
if (el.closest && el.closest("form[method='post'], form[method='POST']")) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAmountLike(el) {
|
||||
var key = String(el.id || "") + " " + String(el.name || "") + " " + String(el.placeholder || "");
|
||||
return /amount|xfer|transfer|划转|数量|金额/i.test(key);
|
||||
}
|
||||
|
||||
function wipeBad(el) {
|
||||
if (!el || isAuthField(el)) return;
|
||||
var v = String(el.value || "").trim();
|
||||
if (!looksLikeUsername(v)) return;
|
||||
var t = String(el.type || "text").toLowerCase();
|
||||
if (t === "number" || isAmountLike(el) || /price|sheets|qty|sl|tp|target|entry|strike/i.test(String(el.id || "") + String(el.name || ""))) {
|
||||
el.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
function harden(el) {
|
||||
if (!el || el.nodeType !== 1) return;
|
||||
if (isAuthField(el)) return;
|
||||
if (el.getAttribute("aria-hidden") === "true") return;
|
||||
if (el.dataset && el.dataset.autofillGuarded === "1") {
|
||||
wipeBad(el);
|
||||
return;
|
||||
}
|
||||
if (el.dataset) el.dataset.autofillGuarded = "1";
|
||||
|
||||
Object.keys(GUARD_ATTRS).forEach(function (k) {
|
||||
var cur = el.getAttribute(k);
|
||||
if (k === "autocomplete" && cur && /^(username|current-password)/i.test(cur)) {
|
||||
return;
|
||||
}
|
||||
// env 密钥框用 new-password 更抗登录密码灌入
|
||||
if (k === "autocomplete" && String(el.type || "").toLowerCase() === "password") {
|
||||
el.setAttribute(k, "new-password");
|
||||
return;
|
||||
}
|
||||
if (!cur || cur === "on") el.setAttribute(k, GUARD_ATTRS[k]);
|
||||
});
|
||||
|
||||
if (String(el.type || "").toLowerCase() === "password" || isAmountLike(el)) {
|
||||
el.setAttribute("readonly", "readonly");
|
||||
el.addEventListener("focus", function () {
|
||||
el.removeAttribute("readonly");
|
||||
});
|
||||
el.addEventListener("blur", function () {
|
||||
if (!el.value) el.setAttribute("readonly", "readonly");
|
||||
});
|
||||
}
|
||||
|
||||
wipeBad(el);
|
||||
setTimeout(function () {
|
||||
wipeBad(el);
|
||||
}, 250);
|
||||
setTimeout(function () {
|
||||
wipeBad(el);
|
||||
}, 900);
|
||||
setTimeout(function () {
|
||||
wipeBad(el);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function scan(root) {
|
||||
var scope = root && root.querySelectorAll ? root : document;
|
||||
var list = scope.querySelectorAll(
|
||||
'input[type="text"], input[type="number"], input[type="search"], input[type="url"], input[type="email"], input[type="tel"], input[type="password"], input:not([type]), textarea'
|
||||
);
|
||||
for (var i = 0; i < list.length; i++) harden(list[i]);
|
||||
}
|
||||
|
||||
function boot() {
|
||||
scan(document);
|
||||
if (typeof MutationObserver === "undefined") return;
|
||||
var obs = new MutationObserver(function (mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var m = mutations[i];
|
||||
if (m.type === "childList") {
|
||||
for (var j = 0; j < m.addedNodes.length; j++) {
|
||||
var n = m.addedNodes[j];
|
||||
if (!n || n.nodeType !== 1) continue;
|
||||
if (n.matches && n.matches("input, textarea")) harden(n);
|
||||
else if (n.querySelectorAll) scan(n);
|
||||
}
|
||||
} else if (m.type === "attributes" && m.target) {
|
||||
harden(m.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
obs.observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["value"],
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
|
||||
window.cmAutofillGuardScan = scan;
|
||||
})();
|
||||
Reference in New Issue
Block a user