js_lis 2.0.2 → 2.0.4
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/VirtualKeyboard.js +185 -3
- package/main.js +43 -43
- package/package.json +1 -1
package/VirtualKeyboard.js
CHANGED
|
@@ -13,15 +13,34 @@ export class VirtualKeyboard {
|
|
|
13
13
|
this.shiftActive = false;
|
|
14
14
|
this.capsLockActive = false;
|
|
15
15
|
this.isScrambled = false;
|
|
16
|
+
this.isVirtualKeyboardActive = false; // Flag to track VK operations
|
|
16
17
|
|
|
17
18
|
// Maintain plaintext buffers per input (not stored in DOM). DOM will store only encrypted text.
|
|
18
19
|
this.inputPlaintextBuffers = new WeakMap();
|
|
19
20
|
|
|
21
|
+
// Security state
|
|
22
|
+
this.security = {
|
|
23
|
+
detectorStatus: "idle",
|
|
24
|
+
suspiciousEvents: [],
|
|
25
|
+
observer: null,
|
|
26
|
+
addSuspicious(event) {
|
|
27
|
+
try {
|
|
28
|
+
this.suspiciousEvents.push({
|
|
29
|
+
time: new Date().toISOString(),
|
|
30
|
+
...event,
|
|
31
|
+
});
|
|
32
|
+
console.warn("[VK Security] Suspicious activity detected:", event);
|
|
33
|
+
} catch (_) {}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
20
37
|
this.initialize();
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
async initialize() {
|
|
24
41
|
try {
|
|
42
|
+
this.applyCSPReportOnly();
|
|
43
|
+
this.startKeyloggerDetector();
|
|
25
44
|
this.render();
|
|
26
45
|
this.initializeInputListeners();
|
|
27
46
|
console.log("VirtualKeyboard initialized successfully.");
|
|
@@ -30,6 +49,113 @@ export class VirtualKeyboard {
|
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
51
|
|
|
52
|
+
// Inject CSP Report-Only meta for runtime visibility into potential violations
|
|
53
|
+
applyCSPReportOnly() {
|
|
54
|
+
// Disabled: Report-Only CSP must be set via HTTP headers. See server middleware in app.js.
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Start MutationObserver and limited listener-hook to detect potential keylogger injections
|
|
59
|
+
startKeyloggerDetector() {
|
|
60
|
+
try {
|
|
61
|
+
const allowlistedScriptHosts = new Set([
|
|
62
|
+
window.location.host,
|
|
63
|
+
"cdnjs.cloudflare.com",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const isSuspiciousScript = (node) => {
|
|
67
|
+
try {
|
|
68
|
+
if (!(node instanceof HTMLScriptElement)) return false;
|
|
69
|
+
const src = node.getAttribute("src") || "";
|
|
70
|
+
if (!src) {
|
|
71
|
+
const code = (node.textContent || "").toLowerCase();
|
|
72
|
+
return /addEventListener\(['\"]key|document\.onkey|keylogger|send\(|fetch\(|xmlhttprequest/i.test(code);
|
|
73
|
+
}
|
|
74
|
+
const url = new URL(src, window.location.href);
|
|
75
|
+
return !Array.from(allowlistedScriptHosts).some((h) => url.host.endsWith(h));
|
|
76
|
+
} catch (_) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const isSuspiciousElement = (node) => {
|
|
82
|
+
try {
|
|
83
|
+
if (!(node instanceof Element)) return false;
|
|
84
|
+
const hasKeyHandlers = node.hasAttribute("onkeydown") || node.hasAttribute("onkeypress") || node.hasAttribute("onkeyup");
|
|
85
|
+
const hiddenFrame = node.tagName === "IFRAME" && (node.getAttribute("sandbox") === null || node.getAttribute("srcdoc") !== null);
|
|
86
|
+
return hasKeyHandlers || hiddenFrame;
|
|
87
|
+
} catch (_) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const quarantineScript = (script) => {
|
|
93
|
+
try {
|
|
94
|
+
script.type = "text/plain";
|
|
95
|
+
script.setAttribute("data-quarantined", "true");
|
|
96
|
+
} catch (_) {}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const observer = new MutationObserver((mutations) => {
|
|
100
|
+
for (const m of mutations) {
|
|
101
|
+
if (m.type === "childList") {
|
|
102
|
+
m.addedNodes.forEach((node) => {
|
|
103
|
+
if (isSuspiciousScript(node)) {
|
|
104
|
+
this.security.addSuspicious({ type: "script", reason: "suspicious source or inline pattern", node });
|
|
105
|
+
quarantineScript(node);
|
|
106
|
+
} else if (isSuspiciousElement(node)) {
|
|
107
|
+
this.security.addSuspicious({ type: "element", reason: "inline key handlers or risky frame", node });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
} else if (m.type === "attributes" && isSuspiciousElement(m.target)) {
|
|
111
|
+
this.security.addSuspicious({ type: "attr", reason: `attribute ${m.attributeName}`, node: m.target });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
observer.observe(document.documentElement, {
|
|
117
|
+
childList: true,
|
|
118
|
+
subtree: true,
|
|
119
|
+
attributes: true,
|
|
120
|
+
attributeFilter: ["onkeydown", "onkeypress", "onkeyup", "src", "type", "sandbox", "srcdoc"],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Hook addEventListener to monitor keyboard listeners registration
|
|
124
|
+
const OriginalAddEventListener = EventTarget.prototype.addEventListener;
|
|
125
|
+
const selfRef = this;
|
|
126
|
+
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
127
|
+
try {
|
|
128
|
+
if (type && /^(key(?:down|up|press))$/i.test(type)) {
|
|
129
|
+
const info = {
|
|
130
|
+
type: "event-listener",
|
|
131
|
+
reason: `keyboard listener registered: ${type}`,
|
|
132
|
+
target: this,
|
|
133
|
+
};
|
|
134
|
+
selfRef.security.addSuspicious(info);
|
|
135
|
+
}
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
return OriginalAddEventListener.call(this, type, listener, options);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.security.observer = observer;
|
|
141
|
+
this.security.detectorStatus = "running";
|
|
142
|
+
|
|
143
|
+
// Expose minimal security API
|
|
144
|
+
window.VKSecurity = {
|
|
145
|
+
getStatus: () => this.security.detectorStatus,
|
|
146
|
+
getEvents: () => [...this.security.suspiciousEvents],
|
|
147
|
+
stop: () => {
|
|
148
|
+
try { observer.disconnect(); } catch (_) {}
|
|
149
|
+
this.security.detectorStatus = "stopped";
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
console.info("[VK Security] Keylogger detector started");
|
|
153
|
+
} catch (err) {
|
|
154
|
+
this.security.detectorStatus = "error";
|
|
155
|
+
console.warn("[VK Security] Detector error:", err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
33
159
|
getLayoutName(layout) {
|
|
34
160
|
switch (layout) {
|
|
35
161
|
case "full":
|
|
@@ -66,6 +192,30 @@ export class VirtualKeyboard {
|
|
|
66
192
|
true
|
|
67
193
|
);
|
|
68
194
|
|
|
195
|
+
// Add real keyboard input listeners to handle encryption
|
|
196
|
+
document.addEventListener("input", (e) => {
|
|
197
|
+
const target = e.target;
|
|
198
|
+
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
|
|
199
|
+
// Only handle real keyboard input if it's not coming from virtual keyboard operations
|
|
200
|
+
if (!this.isVirtualKeyboardActive) {
|
|
201
|
+
this.handleRealKeyboardInput(target);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
document.addEventListener("keydown", (e) => {
|
|
207
|
+
const target = e.target;
|
|
208
|
+
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
|
|
209
|
+
// Handle special keys that might not trigger input event
|
|
210
|
+
if ((e.key === "Backspace" || e.key === "Delete") && !this.isVirtualKeyboardActive) {
|
|
211
|
+
// Wait for the input event to handle the updated value
|
|
212
|
+
setTimeout(() => {
|
|
213
|
+
this.handleRealKeyboardInput(target);
|
|
214
|
+
}, 0);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
69
219
|
const toggle = document.getElementById("toggle");
|
|
70
220
|
if (toggle) {
|
|
71
221
|
toggle.addEventListener("click", this.toggle.bind(this));
|
|
@@ -182,6 +332,10 @@ export class VirtualKeyboard {
|
|
|
182
332
|
// [2]
|
|
183
333
|
async handleKeyPress(keyPressed) {
|
|
184
334
|
if (!this.currentInput) return;
|
|
335
|
+
|
|
336
|
+
// Set flag to indicate virtual keyboard is active
|
|
337
|
+
this.isVirtualKeyboardActive = true;
|
|
338
|
+
|
|
185
339
|
const start = this.currentInput.selectionStart;
|
|
186
340
|
const end = this.currentInput.selectionEnd;
|
|
187
341
|
const buffer = this.getCurrentBuffer();
|
|
@@ -196,7 +350,10 @@ export class VirtualKeyboard {
|
|
|
196
350
|
return char.toLowerCase();
|
|
197
351
|
};
|
|
198
352
|
|
|
199
|
-
if (!keyPressed)
|
|
353
|
+
if (!keyPressed) {
|
|
354
|
+
this.isVirtualKeyboardActive = false;
|
|
355
|
+
return console.error("Invalid key pressed.");
|
|
356
|
+
}
|
|
200
357
|
|
|
201
358
|
if (keyPressed === "scr" || keyPressed === "scrambled") {
|
|
202
359
|
if (this.currentLayout === "en") {
|
|
@@ -250,6 +407,7 @@ export class VirtualKeyboard {
|
|
|
250
407
|
key.classList.toggle("active", this.isScrambled);
|
|
251
408
|
});
|
|
252
409
|
}
|
|
410
|
+
this.isVirtualKeyboardActive = false;
|
|
253
411
|
return;
|
|
254
412
|
}
|
|
255
413
|
|
|
@@ -445,6 +603,9 @@ export class VirtualKeyboard {
|
|
|
445
603
|
this.currentInput.focus();
|
|
446
604
|
const event = new Event("input", { bubbles: true });
|
|
447
605
|
this.currentInput.dispatchEvent(event);
|
|
606
|
+
|
|
607
|
+
// Reset flag after virtual keyboard operation is complete
|
|
608
|
+
this.isVirtualKeyboardActive = false;
|
|
448
609
|
}
|
|
449
610
|
|
|
450
611
|
// [3]
|
|
@@ -482,22 +643,43 @@ export class VirtualKeyboard {
|
|
|
482
643
|
this.currentInput.value = isPassword ? maskChar.repeat(buffer.length) : buffer;
|
|
483
644
|
}
|
|
484
645
|
|
|
646
|
+
// Handle real keyboard input and encrypt it
|
|
647
|
+
handleRealKeyboardInput(input) {
|
|
648
|
+
if (!input) return;
|
|
649
|
+
|
|
650
|
+
// Get the current value from the input
|
|
651
|
+
const currentValue = input.value;
|
|
652
|
+
|
|
653
|
+
// Update the plaintext buffer with the real keyboard input
|
|
654
|
+
this.inputPlaintextBuffers.set(input, currentValue);
|
|
655
|
+
|
|
656
|
+
// Encrypt and store in data-encrypted attribute
|
|
657
|
+
this.updateDomEncrypted(input, currentValue);
|
|
658
|
+
|
|
659
|
+
// console.log("Real keyboard input captured and encrypted for:", input.id || input.name || "unnamed input");
|
|
660
|
+
// console.log("Input type:", input.type, "Value length:", currentValue.length);
|
|
661
|
+
}
|
|
662
|
+
|
|
485
663
|
// Helper: encrypt and store ciphertext in DOM attribute
|
|
486
664
|
updateDomEncrypted(input, plaintext) {
|
|
487
665
|
try {
|
|
488
666
|
const crypto = window.VKCrypto;
|
|
667
|
+
|
|
489
668
|
if (crypto && typeof crypto.encrypt === "function") {
|
|
490
669
|
const cipher = plaintext.length ? crypto.encrypt(plaintext) : "";
|
|
491
670
|
input.dataset.encrypted = cipher;
|
|
492
671
|
} else {
|
|
493
|
-
//
|
|
494
|
-
|
|
672
|
+
// No encryption function available — don't store anything
|
|
673
|
+
console.warn("Encryption unavailable. Not storing plaintext.");
|
|
674
|
+
input.dataset.encrypted = "";
|
|
495
675
|
}
|
|
676
|
+
|
|
496
677
|
} catch (err) {
|
|
497
678
|
console.error("Failed to encrypt input:", err);
|
|
498
679
|
input.dataset.encrypted = "";
|
|
499
680
|
}
|
|
500
681
|
}
|
|
682
|
+
|
|
501
683
|
|
|
502
684
|
// [4]
|
|
503
685
|
unscrambleKeys() {
|
package/main.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { VirtualKeyboard } from './VirtualKeyboard.js';
|
|
2
|
-
|
|
3
|
-
// Provide simple session-scoped AES crypto utilities backed by CryptoJS (loaded globally via CDN)
|
|
4
|
-
function createSessionCrypto() {
|
|
5
|
-
const sessionKey = CryptoJS.lib.WordArray.random(32); // 256-bit key
|
|
6
|
-
return {
|
|
7
|
-
encrypt(plaintext) {
|
|
8
|
-
const iv = CryptoJS.lib.WordArray.random(16);
|
|
9
|
-
const cipher = CryptoJS.AES.encrypt(plaintext, sessionKey, {
|
|
10
|
-
iv,
|
|
11
|
-
mode: CryptoJS.mode.CBC,
|
|
12
|
-
padding: CryptoJS.pad.Pkcs7,
|
|
13
|
-
});
|
|
14
|
-
const ivHex = CryptoJS.enc.Hex.stringify(iv);
|
|
15
|
-
const ctB64 = cipher.toString();
|
|
16
|
-
return `${ivHex}:${ctB64}`;
|
|
17
|
-
},
|
|
18
|
-
decrypt(payload) {
|
|
19
|
-
if (!payload) return "";
|
|
20
|
-
const [ivHex, ctB64] = String(payload).split(":");
|
|
21
|
-
if (!ivHex || !ctB64) return "";
|
|
22
|
-
const iv = CryptoJS.enc.Hex.parse(ivHex);
|
|
23
|
-
const decrypted = CryptoJS.AES.decrypt(ctB64, sessionKey, {
|
|
24
|
-
iv,
|
|
25
|
-
mode: CryptoJS.mode.CBC,
|
|
26
|
-
padding: CryptoJS.pad.Pkcs7,
|
|
27
|
-
});
|
|
28
|
-
return decrypted.toString(CryptoJS.enc.Utf8);
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
window.onload = () => {
|
|
34
|
-
try {
|
|
35
|
-
// Initialize per-page crypto helper
|
|
36
|
-
window.VKCrypto = createSessionCrypto();
|
|
37
|
-
|
|
38
|
-
window.keyboard = new VirtualKeyboard();
|
|
39
|
-
console.log("VirtualKeyboard initialized successfully.");
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error("Error initializing VirtualKeyboard:", error);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
1
|
+
import { VirtualKeyboard } from './VirtualKeyboard.js';
|
|
2
|
+
|
|
3
|
+
// Provide simple session-scoped AES crypto utilities backed by CryptoJS (loaded globally via CDN)
|
|
4
|
+
function createSessionCrypto() {
|
|
5
|
+
const sessionKey = CryptoJS.lib.WordArray.random(32); // 256-bit key
|
|
6
|
+
return {
|
|
7
|
+
encrypt(plaintext) {
|
|
8
|
+
const iv = CryptoJS.lib.WordArray.random(16);
|
|
9
|
+
const cipher = CryptoJS.AES.encrypt(plaintext, sessionKey, {
|
|
10
|
+
iv,
|
|
11
|
+
mode: CryptoJS.mode.CBC,
|
|
12
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
13
|
+
});
|
|
14
|
+
const ivHex = CryptoJS.enc.Hex.stringify(iv);
|
|
15
|
+
const ctB64 = cipher.toString();
|
|
16
|
+
return `${ivHex}:${ctB64}`;
|
|
17
|
+
},
|
|
18
|
+
decrypt(payload) {
|
|
19
|
+
if (!payload) return "";
|
|
20
|
+
const [ivHex, ctB64] = String(payload).split(":");
|
|
21
|
+
if (!ivHex || !ctB64) return "";
|
|
22
|
+
const iv = CryptoJS.enc.Hex.parse(ivHex);
|
|
23
|
+
const decrypted = CryptoJS.AES.decrypt(ctB64, sessionKey, {
|
|
24
|
+
iv,
|
|
25
|
+
mode: CryptoJS.mode.CBC,
|
|
26
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
27
|
+
});
|
|
28
|
+
return decrypted.toString(CryptoJS.enc.Utf8);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
window.onload = () => {
|
|
34
|
+
try {
|
|
35
|
+
// Initialize per-page crypto helper
|
|
36
|
+
window.VKCrypto = createSessionCrypto();
|
|
37
|
+
|
|
38
|
+
window.keyboard = new VirtualKeyboard();
|
|
39
|
+
console.log("VirtualKeyboard initialized successfully.");
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("Error initializing VirtualKeyboard:", error);
|
|
42
|
+
}
|
|
43
|
+
};
|