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.
Files changed (3) hide show
  1. package/VirtualKeyboard.js +185 -3
  2. package/main.js +43 -43
  3. package/package.json +1 -1
@@ -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) return console.error("Invalid key pressed.");
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
- // Fallback: store plaintext if crypto is unavailable (not recommended)
494
- input.dataset.encrypted = plaintext;
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js_lis",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"