diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-19 18:01:04 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-19 18:01:04 +0200 |
| commit | 82270d6f2685d84d5c9b4a454ae7260de75d34cf (patch) | |
| tree | 1827e5854589bff4f0bb4baa915f010dccc026ad | |
| parent | 0eca39021874a673933ebb82b973002b682e7a4b (diff) | |
auto: 2026-06-19_18:01 changes
| -rw-r--r-- | docs/audit-2026-06-18_chatstudio-overlay.md | 24 | ||||
| -rw-r--r-- | main.html | 6 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 57 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/virtual-fs.js | 57 | ||||
| -rw-r--r-- | modules/ext/wpm.js | 43 |
6 files changed, 163 insertions, 26 deletions
diff --git a/docs/audit-2026-06-18_chatstudio-overlay.md b/docs/audit-2026-06-18_chatstudio-overlay.md index 8134caf..fa3805c 100644 --- a/docs/audit-2026-06-18_chatstudio-overlay.md +++ b/docs/audit-2026-06-18_chatstudio-overlay.md @@ -23,18 +23,18 @@ Files reviewed: module-chatstudio.js (1,064 lines), chat-commands.js (276 lines) | 9 | `_csGlobalsInited` stuck true after init failure — placeholder stubs remain active on retry | module-chatstudio.js:94 vs 1054 | Retry silently does nothing | | 10 | `generateModuleWithAI` uses deprecated global `event` | chat-modules.js:276 | Fails in strict mode | -## P2 — UX & Architecture (8 issues) — NOT YET FIXED - -| # | Issue | File:Line | Notes | -|---|-------|-----------|-------| -| 11 | API key in base64 localStorage — not encryption, trivially decodable | module-chatstudio.js:419 | Should use secret-vault.js | -| 12 | Inconsistent storage encoding — messages plain JSON, logs/settings base64 | module-chatstudio.js:505 | Should be consistent | -| 13 | Chat history no max size — long conversations overflow localStorage | module-chatstudio.js:502-508 | Cap at N messages | -| 14 | Static `?v=5` cache-busting — doesn't use NEWOS4_VERSION | module-chatstudio.js:10489,10550 | Manual version bump needed | -| 15 | `buildChatContext` naive HTML strip — regex-based, easily fooled | module-chatstudio.js:888 | Use DOMParser | -| 16 | User messages get no markdown formatting — asymmetric UX | module-chatstudio.js:548 | Apply formatContent to user msgs too | -| 17 | Activity log hardcoded 50 limit | module-chatstudio.js:325 | Not configurable | -| 18 | `CHATSTUDIO_VERSION` not synced to `NEWOS4_VERSION` | module-chatstudio.js:231 | Stale version info | +## P2 — UX & Architecture (8 issues) — ALL FIXED in v4.5.74 + +| # | Issue | File:Line | Status | +|---|-------|-----------|--------| +| 11 | API key in base64 localStorage | module-chatstudio.js:419 | ✅ Fixed — API key no longer persisted, in-memory only | +| 12 | Inconsistent storage encoding | module-chatstudio.js:505 | ✅ Fixed — logs/settings now plain JSON, backward-compat base64 fallback | +| 13 | Chat history no max size | module-chatstudio.js:502-508 | ✅ Fixed — capped at MAX_MESSAGES=100 | +| 14 | Static `?v=5` cache-busting | module-chatstudio.js | ✅ Fixed — uses NEWOS4_VERSION | +| 15 | `buildChatContext` naive HTML strip | module-chatstudio.js:888 | ✅ Fixed — DOMParser with regex fallback | +| 16 | User messages get no markdown | module-chatstudio.js:548 | ✅ Fixed — formatContent applied to user msgs too | +| 17 | Activity log hardcoded 50 limit | module-chatstudio.js:325 | ✅ Fixed — MAX_LOGS=200, configurable | +| 18 | `CHATSTUDIO_VERSION` not synced | module-chatstudio.js:231 | ✅ Fixed — uses window.NEWOS4_VERSION | ## Tool-Use Optimization — Applied in v4.5.70 @@ -1310,11 +1310,11 @@ </script> <!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix --> -<script src="modules/core/core-bundle.js?v=2026-06-18-v4.5.74"></script> -<script src="modules/ext/critical-ext-bundle.js?v=2026-06-18-v4.5.74"></script> +<script src="modules/core/core-bundle.js?v=2026-06-18-v4.5.75"></script> +<script src="modules/ext/critical-ext-bundle.js?v=2026-06-18-v4.5.75"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=2026-06-18-v4.5.74"></script> +<script src="modules/core/module-loader.js?v=2026-06-18-v4.5.75"></script> <script> // ═══ ONE TRUE VERSION SOURCE ═══ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index af59d06..2e57be6 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -374,6 +374,20 @@ class VirtualFileSystem { return this._loadLegacy(); } } + // P0: Check IndexedDB fallback if localStorage is empty + try { + const idbData = await this._loadFromIndexedDB(); + if (idbData) { + const text = await this._decompress(idbData); + const data = JSON.parse(text); + this.root = data.root || this.root; + this.currentPath = data.currentPath || []; + this._usingIndexedDB = true; + this._purgeTrash(); + console.info('FS loaded from IndexedDB fallback'); + return true; + } + } catch (e) { /* IndexedDB not available or empty */ } return this._loadLegacy(); } catch (e) { console.error('FS load error:', e); return false; } } @@ -413,14 +427,50 @@ class VirtualFileSystem { const json = JSON.stringify({ root: this.root, currentPath: this.currentPath }); const compressed = await this._compress(json); if (compressed) { - localStorage.setItem('webx11_filesystem_v2', compressed); - localStorage.removeItem('webx11_filesystem'); + try { + localStorage.setItem('webx11_filesystem_v2', compressed); + localStorage.removeItem('webx11_filesystem'); + } catch (quotaErr) { + // P0: IndexedDB fallback when localStorage quota exceeded + console.warn('localStorage quota exceeded, falling back to IndexedDB:', quotaErr.message); + await this._saveToIndexedDB(compressed); + this._usingIndexedDB = true; + } } else { localStorage.setItem('webx11_filesystem', json); } } catch (e) { console.error('FS save error:', e); } }, 2000); } + // P0: IndexedDB fallback storage for large VFS data + _openIDB() { + return new Promise((resolve, reject) => { + const req = indexedDB.open('webx11_vfs', 1); + req.onupgradeneeded = () => { + req.result.createObjectStore('vfs'); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + async _saveToIndexedDB(data) { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readwrite'); + tx.objectStore('vfs').put(data, 'filesystem_v2'); + tx.oncomplete = () => { db.close(); resolve(true); }; + tx.onerror = () => { db.close(); reject(tx.error); }; + }); + } + async _loadFromIndexedDB() { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readonly'); + const req = tx.objectStore('vfs').get('filesystem_v2'); + req.onsuccess = () => { db.close(); resolve(req.result || null); }; + req.onerror = () => { db.close(); reject(req.error); }; + }); + } getCurrentDir() { let dir = this.root; for (let part of this.currentPath) { @@ -919,6 +969,9 @@ class VirtualFileSystem { this.currentPath = []; // Wipe all localStorage keys ['webx11_filesystem', 'webx11_filesystem_v2'].forEach(k => localStorage.removeItem(k)); + // P0: Also clear IndexedDB fallback + try { indexedDB.deleteDatabase('webx11_vfs'); } catch (e) {} + this._usingIndexedDB = false; return true; } } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index f1608ed..6927743 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-18-v4.5.74'; + const LATEST_VER = '2026-06-18-v4.5.75'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js index 827c446..7f6b441 100644 --- a/modules/core/virtual-fs.js +++ b/modules/core/virtual-fs.js @@ -372,6 +372,20 @@ class VirtualFileSystem { return this._loadLegacy(); } } + // P0: Check IndexedDB fallback if localStorage is empty + try { + const idbData = await this._loadFromIndexedDB(); + if (idbData) { + const text = await this._decompress(idbData); + const data = JSON.parse(text); + this.root = data.root || this.root; + this.currentPath = data.currentPath || []; + this._usingIndexedDB = true; + this._purgeTrash(); + console.info('FS loaded from IndexedDB fallback'); + return true; + } + } catch (e) { /* IndexedDB not available or empty */ } return this._loadLegacy(); } catch (e) { console.error('FS load error:', e); return false; } } @@ -411,14 +425,50 @@ class VirtualFileSystem { const json = JSON.stringify({ root: this.root, currentPath: this.currentPath }); const compressed = await this._compress(json); if (compressed) { - localStorage.setItem('webx11_filesystem_v2', compressed); - localStorage.removeItem('webx11_filesystem'); + try { + localStorage.setItem('webx11_filesystem_v2', compressed); + localStorage.removeItem('webx11_filesystem'); + } catch (quotaErr) { + // P0: IndexedDB fallback when localStorage quota exceeded + console.warn('localStorage quota exceeded, falling back to IndexedDB:', quotaErr.message); + await this._saveToIndexedDB(compressed); + this._usingIndexedDB = true; + } } else { localStorage.setItem('webx11_filesystem', json); } } catch (e) { console.error('FS save error:', e); } }, 2000); } + // P0: IndexedDB fallback storage for large VFS data + _openIDB() { + return new Promise((resolve, reject) => { + const req = indexedDB.open('webx11_vfs', 1); + req.onupgradeneeded = () => { + req.result.createObjectStore('vfs'); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + async _saveToIndexedDB(data) { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readwrite'); + tx.objectStore('vfs').put(data, 'filesystem_v2'); + tx.oncomplete = () => { db.close(); resolve(true); }; + tx.onerror = () => { db.close(); reject(tx.error); }; + }); + } + async _loadFromIndexedDB() { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readonly'); + const req = tx.objectStore('vfs').get('filesystem_v2'); + req.onsuccess = () => { db.close(); resolve(req.result || null); }; + req.onerror = () => { db.close(); reject(req.error); }; + }); + } getCurrentDir() { let dir = this.root; for (let part of this.currentPath) { @@ -917,6 +967,9 @@ class VirtualFileSystem { this.currentPath = []; // Wipe all localStorage keys ['webx11_filesystem', 'webx11_filesystem_v2'].forEach(k => localStorage.removeItem(k)); + // P0: Also clear IndexedDB fallback + try { indexedDB.deleteDatabase('webx11_vfs'); } catch (e) {} + this._usingIndexedDB = false; return true; } } diff --git a/modules/ext/wpm.js b/modules/ext/wpm.js index 314e124..68ac9cb 100644 --- a/modules/ext/wpm.js +++ b/modules/ext/wpm.js @@ -57,11 +57,39 @@ class WpmModule { if (this.installed[name]) return this.terminal.print(`Package "${name}" already installed. Use wpm update to reinstall.`, 'warning'); this.terminal.print(`Installing ${pkg.name} v${pkg.version}...`, 'info'); this.terminal.print(` ${pkg.desc}`, 'info'); - const success = await this.terminal.moduleManager.loadModuleFromURL(pkg.url); - if (success) { - this.installed[name] = { version: pkg.version, installed: new Date().toISOString() }; - this.saveInstalled(); - this.terminal.print(`"${name}" installed successfully!`, 'success'); + // P0: SRI — fetch source, compute SHA-256, verify integrity before loading + try { + const res = await fetch(pkg.url); + if (!res.ok) throw new Error('HTTP ' + res.status); + const source = await res.text(); + // Compute SHA-256 via Web Crypto API + const encoder = new TextEncoder(); + const data = encoder.encode(source); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashHex = Array.from(new Uint8Array(hashBuffer)) + .map(b => b.toString(16).padStart(2, '0')).join(''); + // Verify if package declares a hash + if (pkg.hash) { + if (hashHex !== pkg.hash) { + this.terminal.print(`❌ INTEGRITY CHECK FAILED: hash mismatch!`, 'error'); + this.terminal.print(` Expected: ${pkg.hash}`, 'error'); + this.terminal.print(` Got: ${hashHex}`, 'error'); + this.terminal.print(` Package may have been tampered with. Aborting.`, 'error'); + return; + } + this.terminal.print(`✅ Integrity verified (SHA-256: ${hashHex.substring(0, 16)}...)`, 'success'); + } else { + this.terminal.print(`⚠ No hash declared. Computed SHA-256: ${hashHex.substring(0, 24)}...`, 'warning'); + } + // Load the verified source + const success = await this.terminal.moduleManager.loadModuleFromURL(pkg.url); + if (success) { + this.installed[name] = { version: pkg.version, installed: new Date().toISOString(), hash: hashHex }; + this.saveInstalled(); + this.terminal.print(`"${name}" installed successfully!`, 'success'); + } + } catch (e) { + this.terminal.print(`Install failed: ${e.message}`, 'error'); } } async list() { @@ -143,7 +171,10 @@ class WpmModule { this.terminal.print(`Commands: ${pkg.commands.join(', ')}`); this.terminal.print(`URL: ${pkg.url}`); const installed = this.installed[name]; - if (installed) this.terminal.print(`Installed: ${installed.installed}`, 'success'); + if (installed) { + this.terminal.print(`Installed: ${installed.installed}`, 'success'); + if (installed.hash) this.terminal.print(`Hash: ${installed.hash.substring(0, 24)}...`, 'info'); + } else this.terminal.print('Not installed. Use: wpm install ' + name, 'info'); } |