@corbado/observe 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/client.mjs +1851 -0
- package/dist/analysis/config-manager.mjs +83 -0
- package/dist/analysis/config.mjs +216 -0
- package/dist/analysis/device-info-manager.mjs +153 -0
- package/dist/analysis/device.mjs +562 -0
- package/dist/analysis/index.mjs +118 -0
- package/dist/analysis/logger.mjs +50 -0
- package/dist/analysis/low-events/capture-sequence.mjs +11 -0
- package/dist/analysis/low-events/ceremony-initiator.mjs +26 -0
- package/dist/analysis/low-events/form-input-binding.mjs +46 -0
- package/dist/analysis/low-events/grouped-input-tracker.mjs +133 -0
- package/dist/analysis/low-events/input-change-recorder.mjs +200 -0
- package/dist/analysis/low-events/input-paste-observer.mjs +91 -0
- package/dist/analysis/low-events/input-tracker.mjs +223 -0
- package/dist/analysis/low-events/otp-input-binding.mjs +67 -0
- package/dist/analysis/low-events/shared-window-observer.mjs +225 -0
- package/dist/analysis/low-events/sink.mjs +0 -0
- package/dist/analysis/low-events/viewport-batcher.mjs +42 -0
- package/dist/analysis/low-events/window-tracker.mjs +40 -0
- package/dist/analysis/queue.mjs +887 -0
- package/dist/analysis/readers/device.mjs +73 -0
- package/dist/analysis/readers/meta.mjs +18 -0
- package/dist/analysis/readers/password-manager-dom.mjs +112 -0
- package/dist/analysis/readers/password-manager-extensions.mjs +247 -0
- package/dist/analysis/readers/private-mode.mjs +9 -0
- package/dist/analysis/storage/index.mjs +197 -0
- package/dist/analysis/storage/migration.mjs +56 -0
- package/dist/analysis/store/event-store.mjs +114 -0
- package/dist/analysis/transport.mjs +0 -0
- package/dist/analysis/transports/fetch.mjs +108 -0
- package/dist/analysis/types.mjs +25 -0
- package/dist/analysis/utils/appconfirmation-operation-full.mjs +19 -0
- package/dist/analysis/utils/blocked-user-agents.mjs +37 -0
- package/dist/analysis/utils/captcha-operation-full.mjs +17 -0
- package/dist/analysis/utils/email-link-continuity.mjs +50 -0
- package/dist/analysis/utils/email-link-operation-full.mjs +142 -0
- package/dist/analysis/utils/email-otp-operation-full.mjs +39 -0
- package/dist/analysis/utils/form-operation-full.mjs +81 -0
- package/dist/analysis/utils/key-operation-full.mjs +38 -0
- package/dist/analysis/utils/normalize-error.mjs +29 -0
- package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +46 -0
- package/dist/analysis/utils/operation-full.mjs +235 -0
- package/dist/analysis/utils/passkey-deletion-operation-full.mjs +17 -0
- package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +32 -0
- package/dist/analysis/utils/passkey-login-operation-full.mjs +34 -0
- package/dist/analysis/utils/password-enrollment-operation-full.mjs +27 -0
- package/dist/analysis/utils/password-login-operation-full.mjs +61 -0
- package/dist/analysis/utils/password-manager-sampler.mjs +130 -0
- package/dist/analysis/utils/provide-data-operation-full.mjs +28 -0
- package/dist/analysis/utils/serialize-raw-error.mjs +205 -0
- package/dist/analysis/utils/sms-otp-operation-full.mjs +39 -0
- package/dist/analysis/utils/social-operation-login.mjs +17 -0
- package/dist/analysis/utils/source-path.mjs +39 -0
- package/dist/analysis/utils/sso-operation-login.mjs +37 -0
- package/dist/analysis/utils/totp-operation-full.mjs +35 -0
- package/dist/analysis/utils/trusted-device-operation-full.mjs +100 -0
- package/dist/analysis/utils/web-lock.mjs +52 -0
- package/dist/analysis/utils/webauthn-payloads.mjs +101 -0
- package/dist/analysis/version.mjs +7 -0
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +164 -11
- package/dist/index.d.ts +164 -11
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +7 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/readers/device.ts
|
|
2
|
+
function readUserAgent(_meta) {
|
|
3
|
+
if (typeof navigator === "undefined") {
|
|
4
|
+
return "";
|
|
5
|
+
}
|
|
6
|
+
return navigator.userAgent ?? "";
|
|
7
|
+
}
|
|
8
|
+
async function readBluetoothAvailability(_meta) {
|
|
9
|
+
if (typeof navigator === "undefined" || !navigator.bluetooth) {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
return await navigator.bluetooth.getAvailability();
|
|
13
|
+
}
|
|
14
|
+
async function readUserVerifyingPlatformAuthenticatorAvailable(_meta) {
|
|
15
|
+
if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
19
|
+
}
|
|
20
|
+
async function readConditionalMediationAvailable(_meta) {
|
|
21
|
+
if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.isConditionalMediationAvailable) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
return await PublicKeyCredential.isConditionalMediationAvailable();
|
|
25
|
+
}
|
|
26
|
+
async function readClientCapabilities(_meta) {
|
|
27
|
+
if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.getClientCapabilities) {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
const rawClientCapabilities = await PublicKeyCredential.getClientCapabilities();
|
|
31
|
+
return Object.fromEntries(Object.entries(rawClientCapabilities).map(([key, value]) => [key, value.toString()]));
|
|
32
|
+
}
|
|
33
|
+
async function readJavaScriptHighEntropy(_meta) {
|
|
34
|
+
if (typeof navigator === "undefined" || !navigator.userAgentData) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
const ua = await navigator.userAgentData.getHighEntropyValues(["platformVersion", "model"]);
|
|
38
|
+
const platform = ua.platform;
|
|
39
|
+
const mobile = ua.mobile;
|
|
40
|
+
const platformVersion = ua.platformVersion;
|
|
41
|
+
const model = ua.model || void 0;
|
|
42
|
+
if (!platform || mobile === void 0 || !platformVersion) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
platform,
|
|
47
|
+
mobile,
|
|
48
|
+
platformVersion,
|
|
49
|
+
model
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function readWebdriver(_meta) {
|
|
53
|
+
if (typeof navigator === "undefined") {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
return navigator.webdriver;
|
|
57
|
+
}
|
|
58
|
+
function readMaxTouchPoints(_meta) {
|
|
59
|
+
if (typeof navigator === "undefined" || typeof navigator.maxTouchPoints !== "number") {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
return navigator.maxTouchPoints;
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
readBluetoothAvailability,
|
|
66
|
+
readClientCapabilities,
|
|
67
|
+
readConditionalMediationAvailable,
|
|
68
|
+
readJavaScriptHighEntropy,
|
|
69
|
+
readMaxTouchPoints,
|
|
70
|
+
readUserAgent,
|
|
71
|
+
readUserVerifyingPlatformAuthenticatorAvailable,
|
|
72
|
+
readWebdriver
|
|
73
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/readers/password-manager-dom.ts
|
|
2
|
+
var SIGNAL_TAG_QUERIES = [
|
|
3
|
+
{ query: "com-1password-button", eventType: "pa-1" },
|
|
4
|
+
{ query: "com-1password-menu", eventType: "pa-2" },
|
|
5
|
+
{ query: "com-1password-notification", eventType: "pa-3" }
|
|
6
|
+
];
|
|
7
|
+
var SIGNAL_ATTR_SELECTOR = "[data-lastpass-root], [data-lp], [data-lpignore], [data-lpfieldtype]";
|
|
8
|
+
var SIGNAL_ATTR_EVENT_TYPE = "pa-4";
|
|
9
|
+
var POPOVER_MANUAL_SELECTOR = '[popover="manual"]';
|
|
10
|
+
var PROFILE_A_MIN_STYLE_LENGTH = 400;
|
|
11
|
+
var PROFILE_A_MIN_INITIAL_IMPORTANT = 20;
|
|
12
|
+
var PROFILE_A_EVENT_TYPE = "pa-5";
|
|
13
|
+
var PROFILE_B_EVENT_TYPE = "pa-6";
|
|
14
|
+
var safeQueryAll = (root, selector) => {
|
|
15
|
+
try {
|
|
16
|
+
return Array.from(root.querySelectorAll(selector));
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var isPopoverProfileA = (el) => {
|
|
22
|
+
if (el.getAttribute("popover") !== "manual") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const style = el.getAttribute("style") ?? "";
|
|
26
|
+
if (style.length < PROFILE_A_MIN_STYLE_LENGTH) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (!/z-index:\s*2147483647\b/i.test(style)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!/position:\s*fixed\b/i.test(style)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (!/display:\s*block\b/i.test(style)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const initialImportantHits = style.match(/initial\s*!important/gi);
|
|
39
|
+
if ((initialImportantHits?.length ?? 0) < PROFILE_A_MIN_INITIAL_IMPORTANT) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
var isPopoverProfileB = (el) => {
|
|
45
|
+
if (isPopoverProfileA(el)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (el.getAttribute("popover") !== "manual") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const style = el.getAttribute("style") ?? "";
|
|
52
|
+
if (!/z-index:\s*2147483647\b/i.test(style)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (!/position:\s*fixed\b/i.test(style)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (!/backdrop-filter:\s*[^;]*blur\(\s*20px\s*\)/i.test(style)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (!/background-color:\s*rgba\(\s*35\s*,\s*35\s*,\s*35\s*,/i.test(style)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
var forEachScanRoot = (cb) => {
|
|
67
|
+
cb(document);
|
|
68
|
+
for (const host of Array.from(document.querySelectorAll("*"))) {
|
|
69
|
+
const shadowRoot = host.shadowRoot;
|
|
70
|
+
if (shadowRoot) {
|
|
71
|
+
cb(shadowRoot);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var scanInputEnvSignals = () => {
|
|
76
|
+
const matched = /* @__PURE__ */ new Set();
|
|
77
|
+
if (typeof document === "undefined") {
|
|
78
|
+
return matched;
|
|
79
|
+
}
|
|
80
|
+
forEachScanRoot((root) => {
|
|
81
|
+
for (const entry of SIGNAL_TAG_QUERIES) {
|
|
82
|
+
if (matched.has(entry.eventType)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (safeQueryAll(root, entry.query).length > 0) {
|
|
86
|
+
matched.add(entry.eventType);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!matched.has(SIGNAL_ATTR_EVENT_TYPE) && safeQueryAll(root, SIGNAL_ATTR_SELECTOR).length > 0) {
|
|
90
|
+
matched.add(SIGNAL_ATTR_EVENT_TYPE);
|
|
91
|
+
}
|
|
92
|
+
if (!matched.has(PROFILE_A_EVENT_TYPE) || !matched.has(PROFILE_B_EVENT_TYPE)) {
|
|
93
|
+
for (const el of safeQueryAll(root, POPOVER_MANUAL_SELECTOR)) {
|
|
94
|
+
if (!matched.has(PROFILE_A_EVENT_TYPE) && isPopoverProfileA(el)) {
|
|
95
|
+
matched.add(PROFILE_A_EVENT_TYPE);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!matched.has(PROFILE_B_EVENT_TYPE) && isPopoverProfileB(el)) {
|
|
99
|
+
matched.add(PROFILE_B_EVENT_TYPE);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return matched;
|
|
105
|
+
};
|
|
106
|
+
function readPasswordManagerDomSignals(_meta) {
|
|
107
|
+
return scanInputEnvSignals();
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
readPasswordManagerDomSignals,
|
|
111
|
+
scanInputEnvSignals
|
|
112
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/readers/password-manager-extensions.ts
|
|
2
|
+
var ONEPASSWORD_SCRIPT = "/inline/injected/webauthn-listeners.js";
|
|
3
|
+
var DASHLANE_STACK = /\/dashlane-(?:shadow-dom|top-layer)-page-script\.js/;
|
|
4
|
+
var KASPERSKY_SCRIPT = /-extension:\/\/[^/]+\/content-scripts\/passkey_proxy\/index\.js$/;
|
|
5
|
+
var KEEPER_STACK = /-extension:\/\/[^/]+\/content_scripts\/shadow-dom\/shadowAttachNotifier\.js:/;
|
|
6
|
+
var NORDPASS_STACK = /-extension:\/\/[^/]+\/js\/nordpass-script\.js:/;
|
|
7
|
+
var NORDPASS_PASSKEYS_KEY = /-extension:\/\/[^/]+\/js\/passkeys\.js:/;
|
|
8
|
+
var KEEPER_SOURCE = /["'`](?:keeper:shadow-root-attached|webauthn-(?:get|create)-request)["'`]/;
|
|
9
|
+
var PROTONPASS_SOURCE = /Intercept cancelled for `(?:get|create)`|["'`](?:conditionalMediationAvailable|verifyingPlatformAuthenticatorAvailable)["'`]/;
|
|
10
|
+
var sentryDebugIdKeys = () => Object.keys(window._sentryDebugIds ?? {});
|
|
11
|
+
var stackOf = (fn) => {
|
|
12
|
+
try {
|
|
13
|
+
fn();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return String(error?.stack ?? "");
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
};
|
|
19
|
+
var attachShadowStack = () => stackOf(() => Element.prototype.attachShadow.call(null, { mode: "open" }));
|
|
20
|
+
var has1PasswordAccessor = () => {
|
|
21
|
+
const hook = Object.getOwnPropertyDescriptor(navigator.credentials ?? {}, "get");
|
|
22
|
+
if (!hook?.get || !hook.set || hook.configurable) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const marker = () => void 0;
|
|
26
|
+
const read = hook.get.call({ _get: marker });
|
|
27
|
+
return read === marker || typeof read === "function" && read === hook.get.call({}) && !Function.prototype.toString.call(read).includes("[native code]");
|
|
28
|
+
};
|
|
29
|
+
var credentialSlots = (...keys) => {
|
|
30
|
+
const credentials = navigator.credentials;
|
|
31
|
+
return credentials ? keys.map((key) => Object.getOwnPropertyDescriptor(credentials, key)?.value) : [];
|
|
32
|
+
};
|
|
33
|
+
var hasBitwardenOverride = () => [navigator.credentials?.get, ...credentialSlots("get", "_get")].some(
|
|
34
|
+
(method) => typeof method === "function" && method.name === "getWebAuthnCredential"
|
|
35
|
+
);
|
|
36
|
+
var probeBitwardenKeyframes = () => {
|
|
37
|
+
const root = document.documentElement;
|
|
38
|
+
const sheets = document.adoptedStyleSheets;
|
|
39
|
+
if (!root || typeof root.getAnimations !== "function" || typeof sheets?.push !== "function") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (root.getAnimations().length > 0) return false;
|
|
43
|
+
const sheet = new CSSStyleSheet();
|
|
44
|
+
sheet.replaceSync(":where(:root){animation:bitwardenfill 1s 1000s paused}");
|
|
45
|
+
sheets.push(sheet);
|
|
46
|
+
try {
|
|
47
|
+
const found = root.getAnimations().filter((animation) => animation.animationName === "bitwardenfill");
|
|
48
|
+
found.forEach((animation) => animation.cancel());
|
|
49
|
+
return found.length > 0;
|
|
50
|
+
} finally {
|
|
51
|
+
const index = sheets.indexOf(sheet);
|
|
52
|
+
if (index >= 0) sheets.splice(index, 1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var bitwardenKeyframes;
|
|
56
|
+
var hasBitwardenKeyframes = () => bitwardenKeyframes ??= probeBitwardenKeyframes();
|
|
57
|
+
var hasDashlaneInstanceTrap = () => {
|
|
58
|
+
const credential = typeof PublicKeyCredential === "undefined" ? void 0 : PublicKeyCredential;
|
|
59
|
+
if (typeof credential !== "function" || !Object.prototype.hasOwnProperty.call(credential, Symbol.hasInstance)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const result = credential[Symbol.hasInstance]({});
|
|
63
|
+
if (!(result instanceof Promise)) return false;
|
|
64
|
+
result.catch(() => void 0);
|
|
65
|
+
return true;
|
|
66
|
+
};
|
|
67
|
+
var nordPassObserverStack = () => {
|
|
68
|
+
const target = {
|
|
69
|
+
get nodeType() {
|
|
70
|
+
throw new Error("probe");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return stackOf(() => MutationObserver.prototype.observe.call(null, target));
|
|
74
|
+
};
|
|
75
|
+
var hasNordPassObserverProxy = () => {
|
|
76
|
+
const sentinel = new Error("probe");
|
|
77
|
+
const read = [];
|
|
78
|
+
const target = {
|
|
79
|
+
get nodeType() {
|
|
80
|
+
read.push("nodeType");
|
|
81
|
+
return 1;
|
|
82
|
+
},
|
|
83
|
+
get ownerDocument() {
|
|
84
|
+
read.push("ownerDocument");
|
|
85
|
+
throw sentinel;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
MutationObserver.prototype.observe.call(null, target);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return error === sentinel && read.join() === "nodeType,ownerDocument";
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
};
|
|
95
|
+
var hasKeeperOverride = () => {
|
|
96
|
+
const methods = [
|
|
97
|
+
Element.prototype.attachShadow,
|
|
98
|
+
navigator.credentials?.get,
|
|
99
|
+
navigator.credentials?.create,
|
|
100
|
+
...credentialSlots("get", "_get", "create")
|
|
101
|
+
];
|
|
102
|
+
return methods.some((m) => typeof m === "function" && KEEPER_SOURCE.test(Function.prototype.toString.call(m)));
|
|
103
|
+
};
|
|
104
|
+
var hasLastPassRoot = () => {
|
|
105
|
+
let root = document;
|
|
106
|
+
while (root) {
|
|
107
|
+
if (root.querySelector("[data-lastpass-icon-root], [data-lastpass-root]")) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
root = root.activeElement?.shadowRoot;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
var hasProtonPassOverride = () => {
|
|
115
|
+
const credential = typeof PublicKeyCredential === "undefined" ? void 0 : PublicKeyCredential;
|
|
116
|
+
const methods = [
|
|
117
|
+
navigator.credentials?.get,
|
|
118
|
+
...credentialSlots("get", "_get"),
|
|
119
|
+
credential?.isConditionalMediationAvailable,
|
|
120
|
+
credential?._isConditionalMediationAvailable,
|
|
121
|
+
credential?.isUserVerifyingPlatformAuthenticatorAvailable,
|
|
122
|
+
credential?._isUserVerifyingPlatformAuthenticatorAvailable
|
|
123
|
+
];
|
|
124
|
+
return methods.some((m) => typeof m === "function" && PROTONPASS_SOURCE.test(Function.prototype.toString.call(m)));
|
|
125
|
+
};
|
|
126
|
+
var PROBES = [
|
|
127
|
+
{
|
|
128
|
+
name: "1password",
|
|
129
|
+
signal: "in-field button",
|
|
130
|
+
signedIn: true,
|
|
131
|
+
// Renders only for a signed-in account (locked or not), while a login field is focused.
|
|
132
|
+
test: () => document.querySelector("com-1password-button") !== null
|
|
133
|
+
},
|
|
134
|
+
{ name: "1password", signal: "credentials.get accessor", test: has1PasswordAccessor },
|
|
135
|
+
{
|
|
136
|
+
name: "1password",
|
|
137
|
+
signal: "Sentry debug id",
|
|
138
|
+
// Sentry debug-id keys are stack strings naming the injected script.
|
|
139
|
+
test: () => sentryDebugIdKeys().some((key) => key.includes(ONEPASSWORD_SCRIPT))
|
|
140
|
+
},
|
|
141
|
+
{ name: "bitwarden", signal: "passkey override", signedIn: true, test: hasBitwardenOverride },
|
|
142
|
+
{ name: "bitwarden", signal: "autofill keyframes", test: hasBitwardenKeyframes },
|
|
143
|
+
{
|
|
144
|
+
name: "dashlane",
|
|
145
|
+
signal: "injected script tag",
|
|
146
|
+
// Edge injects its page scripts as tags (https only); they are in the DOM before they execute.
|
|
147
|
+
test: () => document.querySelector('script[src*="-extension://"][src*="/dashlane-"][src$="-page-script.js"]') !== null
|
|
148
|
+
},
|
|
149
|
+
{ name: "dashlane", signal: "PublicKeyCredential hasInstance trap", test: hasDashlaneInstanceTrap },
|
|
150
|
+
{
|
|
151
|
+
name: "dashlane",
|
|
152
|
+
signal: "page script stack",
|
|
153
|
+
// The shadow-dom and top-layer page scripts (http too) wrap these methods; a null receiver throws inside them.
|
|
154
|
+
test: () => DASHLANE_STACK.test(attachShadowStack()) || typeof HTMLDialogElement !== "undefined" && DASHLANE_STACK.test(
|
|
155
|
+
stackOf(() => HTMLDialogElement.prototype.showModal.call(null))
|
|
156
|
+
)
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "kaspersky",
|
|
160
|
+
signal: "passkey proxy flag",
|
|
161
|
+
// Set in every https frame at document_start, with or without its desktop app.
|
|
162
|
+
test: () => window.wasKPMKeyPassProxyInited === true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "kaspersky",
|
|
166
|
+
signal: "injected script tag",
|
|
167
|
+
// Firefox injects the proxy as a script tag, which is in the DOM before it runs.
|
|
168
|
+
test: () => KASPERSKY_SCRIPT.test(
|
|
169
|
+
document.querySelector('script[src*="/content-scripts/passkey_proxy/"]')?.src ?? ""
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "keeper",
|
|
174
|
+
signal: "attachShadow stack",
|
|
175
|
+
// Wrapped at document_start; a null receiver throws inside the wrapper before it notifies.
|
|
176
|
+
test: () => KEEPER_STACK.test(attachShadowStack())
|
|
177
|
+
},
|
|
178
|
+
{ name: "keeper", signal: "override source", test: hasKeeperOverride },
|
|
179
|
+
{
|
|
180
|
+
name: "keeper",
|
|
181
|
+
signal: "field lock",
|
|
182
|
+
// Tagged on every login field it decorates, signed in or not, shortly after load.
|
|
183
|
+
test: () => document.querySelector("[data-keeper-lock-id]") !== null
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "lastpass",
|
|
187
|
+
signal: "passkey override",
|
|
188
|
+
signedIn: true,
|
|
189
|
+
// Registered at document_start (https) while the vault is unlocked and its passkey support is enabled.
|
|
190
|
+
test: () => typeof window.__nativeCredentialsGet === "function"
|
|
191
|
+
},
|
|
192
|
+
// Otherwise nothing runs in the page until it decorates a login field, shortly after load.
|
|
193
|
+
{ name: "lastpass", signal: "icon root", test: hasLastPassRoot },
|
|
194
|
+
{ name: "nordpass", signal: "observer proxy stack", test: () => NORDPASS_STACK.test(nordPassObserverStack()) },
|
|
195
|
+
{ name: "nordpass", signal: "observer read order", test: hasNordPassObserverProxy },
|
|
196
|
+
{
|
|
197
|
+
name: "nordpass",
|
|
198
|
+
signal: "passkeys Sentry debug id",
|
|
199
|
+
test: () => sentryDebugIdKeys().some((key) => NORDPASS_PASSKEYS_KEY.test(key))
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "nordpass",
|
|
203
|
+
signal: "Sentry debug id",
|
|
204
|
+
test: () => sentryDebugIdKeys().some((key) => NORDPASS_STACK.test(key))
|
|
205
|
+
},
|
|
206
|
+
{ name: "protonpass", signal: "WebAuthn override source", test: hasProtonPassOverride },
|
|
207
|
+
{
|
|
208
|
+
name: "protonpass",
|
|
209
|
+
signal: "field icon",
|
|
210
|
+
// Plain http, frames and later wrappers: its root and field-icon elements once its client script runs.
|
|
211
|
+
test: () => document.querySelector("[data-protonpass-role]") !== null
|
|
212
|
+
}
|
|
213
|
+
];
|
|
214
|
+
function probePasswordManagerExtensions() {
|
|
215
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return PROBES.map(({ name, signal, signedIn, test }) => {
|
|
219
|
+
try {
|
|
220
|
+
return { name, signal, signedIn, matched: test() };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return { name, signal, signedIn, matched: false, error };
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function detectPasswordManagerExtensions() {
|
|
227
|
+
const detected = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const { name, signedIn, matched } of probePasswordManagerExtensions()) {
|
|
229
|
+
if (!matched) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const extension = detected.get(name) ?? { name };
|
|
233
|
+
if (signedIn) {
|
|
234
|
+
extension.signedIn = true;
|
|
235
|
+
}
|
|
236
|
+
detected.set(name, extension);
|
|
237
|
+
}
|
|
238
|
+
return [...detected.values()];
|
|
239
|
+
}
|
|
240
|
+
function readPasswordManagerExtensions(_meta) {
|
|
241
|
+
return detectPasswordManagerExtensions();
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
detectPasswordManagerExtensions,
|
|
245
|
+
probePasswordManagerExtensions,
|
|
246
|
+
readPasswordManagerExtensions
|
|
247
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// src/storage/index.ts
|
|
2
|
+
function namespacedKey(key, namespace) {
|
|
3
|
+
return `${key}_${namespace}`;
|
|
4
|
+
}
|
|
5
|
+
var NamespacedStorage = class {
|
|
6
|
+
constructor(engine, namespace) {
|
|
7
|
+
this.engine = engine;
|
|
8
|
+
this.namespace = namespace;
|
|
9
|
+
}
|
|
10
|
+
getItem(key) {
|
|
11
|
+
return this.engine.getItem(namespacedKey(key, this.namespace));
|
|
12
|
+
}
|
|
13
|
+
setItem(key, value) {
|
|
14
|
+
this.engine.setItem(namespacedKey(key, this.namespace), value);
|
|
15
|
+
}
|
|
16
|
+
removeItem(key) {
|
|
17
|
+
this.engine.removeItem(namespacedKey(key, this.namespace));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var LocalStorage = class {
|
|
21
|
+
constructor(logger) {
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Error situations:
|
|
26
|
+
* - localStorage is not available: return null
|
|
27
|
+
* - localStorage.getItem throws: catch exception, log error and return null
|
|
28
|
+
* - JSON.parse throws: catch exception, log error and return null
|
|
29
|
+
*/
|
|
30
|
+
getItem(key) {
|
|
31
|
+
try {
|
|
32
|
+
if (typeof localStorage === "undefined") return null;
|
|
33
|
+
const item = localStorage.getItem(key);
|
|
34
|
+
if (item) {
|
|
35
|
+
return JSON.parse(item);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.logger.error(`LocalStorage.getItem failed for key "${key}"`, error);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Error situations:
|
|
45
|
+
* - localStorage is not available: return without writing
|
|
46
|
+
* - localStorage.setItem throws: catch exception and log error
|
|
47
|
+
* - JSON.stringify throws: catch exception and log error
|
|
48
|
+
*/
|
|
49
|
+
setItem(key, value) {
|
|
50
|
+
try {
|
|
51
|
+
if (typeof localStorage === "undefined") return;
|
|
52
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.logger.error(`LocalStorage.setItem failed for key "${key}"`, error);
|
|
55
|
+
this.logger.reportTelemetry?.("error", `localStorage.setItem failed: ${key}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Error situations:
|
|
60
|
+
* - localStorage is not available: return without writing
|
|
61
|
+
* - localStorage.removeItem throws: catch exception and log error
|
|
62
|
+
*/
|
|
63
|
+
removeItem(key) {
|
|
64
|
+
try {
|
|
65
|
+
if (typeof localStorage === "undefined") return;
|
|
66
|
+
localStorage.removeItem(key);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.logger.error(`LocalStorage.removeItem failed for key "${key}"`, error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var CookieStorage = class {
|
|
73
|
+
constructor(logger, domain) {
|
|
74
|
+
this.logger = logger;
|
|
75
|
+
this.domain = domain;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Error situations:
|
|
79
|
+
* - document is not available: return null
|
|
80
|
+
* - document.cookie read throws: catch exception, log error and return null
|
|
81
|
+
* - decodeURIComponent throws: catch exception, log error and return null
|
|
82
|
+
* - JSON.parse throws: catch exception, log error and return null
|
|
83
|
+
*/
|
|
84
|
+
getItem(key) {
|
|
85
|
+
try {
|
|
86
|
+
if (typeof document === "undefined") return null;
|
|
87
|
+
const name = key + "=";
|
|
88
|
+
const parts = document.cookie.split(";");
|
|
89
|
+
for (let i = 0; i < parts.length; i++) {
|
|
90
|
+
const part = parts[i].trim();
|
|
91
|
+
if (part.indexOf(name) === 0) {
|
|
92
|
+
const rawValue = part.substring(name.length);
|
|
93
|
+
return JSON.parse(decodeURIComponent(rawValue));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.logger.error(`CookieStorage.getItem failed for key "${key}"`, error);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Error situations:
|
|
104
|
+
* - document is not available: return without writing
|
|
105
|
+
* - document.cookie write throws: catch exception and log error
|
|
106
|
+
* - JSON.stringify throws: catch exception and log error
|
|
107
|
+
*/
|
|
108
|
+
setItem(key, value) {
|
|
109
|
+
try {
|
|
110
|
+
if (typeof document === "undefined") return;
|
|
111
|
+
let cookieString = `${key}=${encodeURIComponent(JSON.stringify(value))}; path=/; SameSite=Lax`;
|
|
112
|
+
if (this.domain) {
|
|
113
|
+
cookieString += `; domain=${this.domain}`;
|
|
114
|
+
}
|
|
115
|
+
document.cookie = cookieString;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
this.logger.error(`CookieStorage.setItem failed for key "${key}"`, error);
|
|
118
|
+
this.logger.reportTelemetry?.("error", `cookie.setItem failed: ${key}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Error situations:
|
|
123
|
+
* - document is not available: return without writing
|
|
124
|
+
* - document.cookie write throws: catch exception and log error
|
|
125
|
+
*/
|
|
126
|
+
removeItem(key) {
|
|
127
|
+
try {
|
|
128
|
+
if (typeof document === "undefined") return;
|
|
129
|
+
let cookieString = `${key}=; Max-Age=-99999999; path=/`;
|
|
130
|
+
if (this.domain) {
|
|
131
|
+
cookieString += `; domain=${this.domain}`;
|
|
132
|
+
}
|
|
133
|
+
document.cookie = cookieString;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.logger.error(`CookieStorage.removeItem failed for key "${key}"`, error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var SessionStorage = class {
|
|
140
|
+
constructor(logger) {
|
|
141
|
+
this.logger = logger;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Error situations:
|
|
145
|
+
* - sessionStorage is not available: return null
|
|
146
|
+
* - sessionStorage.getItem throws: catch exception, log error and return null
|
|
147
|
+
* - JSON.parse throws: catch exception, log error and return null
|
|
148
|
+
*/
|
|
149
|
+
getItem(key) {
|
|
150
|
+
try {
|
|
151
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
152
|
+
const item = sessionStorage.getItem(key);
|
|
153
|
+
if (item) {
|
|
154
|
+
return JSON.parse(item);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.error(`SessionStorage.getItem failed for key "${key}"`, error);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Error situations:
|
|
164
|
+
* - sessionStorage is not available: return without writing
|
|
165
|
+
* - sessionStorage.setItem throws: catch exception and log error
|
|
166
|
+
* - JSON.stringify throws: catch exception and log error
|
|
167
|
+
*/
|
|
168
|
+
setItem(key, value) {
|
|
169
|
+
try {
|
|
170
|
+
if (typeof sessionStorage === "undefined") return;
|
|
171
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
172
|
+
} catch (error) {
|
|
173
|
+
this.logger.error(`SessionStorage.setItem failed for key "${key}"`, error);
|
|
174
|
+
this.logger.reportTelemetry?.("error", `sessionStorage.setItem failed: ${key}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Error situations:
|
|
179
|
+
* - sessionStorage is not available: return without writing
|
|
180
|
+
* - sessionStorage.removeItem throws: catch exception and log error
|
|
181
|
+
*/
|
|
182
|
+
removeItem(key) {
|
|
183
|
+
try {
|
|
184
|
+
if (typeof sessionStorage === "undefined") return;
|
|
185
|
+
sessionStorage.removeItem(key);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
this.logger.error(`SessionStorage.removeItem failed for key "${key}"`, error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
export {
|
|
192
|
+
CookieStorage,
|
|
193
|
+
LocalStorage,
|
|
194
|
+
NamespacedStorage,
|
|
195
|
+
SessionStorage,
|
|
196
|
+
namespacedKey
|
|
197
|
+
};
|