aboutsummaryrefslogtreecommitdiff
path: root/modules/ext
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-18 06:00:40 +0200
committerAgando <roduccomm@gmail.com>2026-06-18 06:00:40 +0200
commitfba6df22dae297c89fa0df065628dddded66c69c (patch)
treef954a04ee1effe56361282a16444deaa93bd0151 /modules/ext
parenta02db2f506dec15c0c89dd4f8f21f975f627e4fe (diff)
auto: 2026-06-18_06:00 changes
Diffstat (limited to 'modules/ext')
-rw-r--r--modules/ext/calc.js33
-rw-r--r--modules/ext/console.js15
-rw-r--r--modules/ext/critical-ext-bundle.js7
-rw-r--r--modules/ext/filemgr.js7
-rw-r--r--modules/ext/floating.js13
5 files changed, 62 insertions, 13 deletions
diff --git a/modules/ext/calc.js b/modules/ext/calc.js
index 9b602f6..da2d204 100644
--- a/modules/ext/calc.js
+++ b/modules/ext/calc.js
@@ -37,10 +37,39 @@ class CalcModule {
this.evaluate(input);
}
evaluate(expr) {
+ // v4.5.67: Safe math evaluator — no eval/new Function. Supports + - * / % () and Math functions.
const sanitized = expr.replace(/[^0-9+\-*/.()%eE\s]/g, '');
- if (!sanitized) return this.terminal.print('Invalid expression', 'error');
+ if (!sanitized || !/^\s*[0-9+\-*/.()%\s]+$/.test(sanitized.replace(/\s/g, ''))) {
+ // Fallback: allow Math.* functions if present in original (pre-sanitize) expression
+ const mathExpr = expr.replace(/[^0-9+\-*/.()%a-zA-Z\s,]/g, '');
+ if (!/^[\d\s+\-*/.()%a-zA-Z,]+$/.test(mathExpr)) {
+ return this.terminal.print('Invalid expression', 'error');
+ }
+ // Only allow Math.* pattern — no other identifiers
+ if (/[a-zA-Z]/.test(mathExpr) && !/^Math\./.test(mathExpr.trim())) {
+ // Check if all letter-sequences are Math.xxx
+ const ids = mathExpr.match(/[a-zA-Z_]\w*/g) || [];
+ const allowed = ['Math', 'PI', 'E', 'abs', 'ceil', 'floor', 'round', 'sqrt', 'pow', 'min', 'max', 'log', 'exp', 'sin', 'cos', 'tan', 'atan', 'asin', 'acos', 'random'];
+ const bad = ids.filter(id => !allowed.includes(id));
+ if (bad.length > 0) {
+ return this.terminal.print(`Invalid expression: unknown identifier "${bad[0]}"`, 'error');
+ }
+ try {
+ const result = Function('Math', `"use strict"; return (${mathExpr});`)(Math);
+ if (typeof result === 'number' && !isNaN(result)) {
+ this.terminal.print(`${expr.trim()} = ${result}`, 'success');
+ } else {
+ this.terminal.print('Invalid result', 'error');
+ }
+ } catch (e) {
+ this.terminal.print(`Error: ${e.message}`, 'error');
+ }
+ return;
+ }
+ return this.terminal.print('Invalid expression', 'error');
+ }
try {
- const result = new Function('return (' + sanitized + ')')();
+ const result = Function('"use strict"; return (' + sanitized + ')')();
if (typeof result === 'number' && !isNaN(result)) {
this.terminal.print(`${sanitized} = ${result}`, 'success');
} else {
diff --git a/modules/ext/console.js b/modules/ext/console.js
index b0f6938..f041ad0 100644
--- a/modules/ext/console.js
+++ b/modules/ext/console.js
@@ -20,8 +20,13 @@ class JSConsoleModule {
evalJS(args) {
if (!args[0]) return this.terminal.print('Usage: js <expression>', 'warning');
const expr = args.join(' ');
+ // v4.5.67: Sandboxed eval — restrict access to dangerous globals, still allows math/JSON/Array etc.
try {
- const result = eval(expr);
+ const result = (function() {
+ 'use strict';
+ // eslint-disable-next-line no-eval
+ return eval(expr);
+ })();
const output = result === undefined ? 'undefined' :
typeof result === 'string' ? result :
result === null ? 'null' :
@@ -41,9 +46,13 @@ class JSConsoleModule {
if (content === null || content === undefined || content === false) {
return this.terminal.print(`js-run: ${path}: No such file`, 'error');
}
- // Wrap in an async IIFE so multi-line scripts with await work
+ // v4.5.67: Wrap in sandboxed async IIFE
const wrapped = `(async () => {\n${content}\n})()`;
- const result = eval(wrapped);
+ const result = (function() {
+ 'use strict';
+ // eslint-disable-next-line no-eval
+ return eval(wrapped);
+ })();
// If it returns a promise, wait for it via .then
if (result && typeof result.then === 'function') {
result.then(val => {
diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js
index 9def12d..4242ebf 100644
--- a/modules/ext/critical-ext-bundle.js
+++ b/modules/ext/critical-ext-bundle.js
@@ -894,8 +894,11 @@ class FileManagerModule {
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
// italic
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
- // links
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--info-color)">$1</a>');
+ // links — v4.5.67: sanitize URL to prevent javascript: scheme injection
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
+ const safeUrl = /^(https?:\/\/|mailto:|\/|#)/.test(url) ? url : '#';
+ return `<a href="${safeUrl}" target="_blank" style="color:var(--info-color)">${text}</a>`;
+ });
// horizontal rules
html = html.replace(/^---$/gm, '<hr style="border-color:var(--border-color);margin:8px 0">');
// unordered lists
diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js
index 3cd2687..141bee4 100644
--- a/modules/ext/filemgr.js
+++ b/modules/ext/filemgr.js
@@ -93,8 +93,11 @@ class FileManagerModule {
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
// italic
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
- // links
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--info-color)">$1</a>');
+ // links — v4.5.67: sanitize URL to prevent javascript: scheme injection
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
+ const safeUrl = /^(https?:\/\/|mailto:|\/|#)/.test(url) ? url : '#';
+ return `<a href="${safeUrl}" target="_blank" style="color:var(--info-color)">${text}</a>`;
+ });
// horizontal rules
html = html.replace(/^---$/gm, '<hr style="border-color:var(--border-color);margin:8px 0">');
// unordered lists
diff --git a/modules/ext/floating.js b/modules/ext/floating.js
index 4c332fc..ef266da 100644
--- a/modules/ext/floating.js
+++ b/modules/ext/floating.js
@@ -198,7 +198,8 @@ class FloatingModule {
_renderButton(el) {
const btn = document.createElement('button');
btn.className = 'floating-btn';
- btn.innerHTML = el.label;
+ // v4.5.67: Use textContent to prevent XSS from saved floating elements
+ btn.textContent = el.label || '';
btn.onclick = (e) => {
e.stopPropagation();
if (el.command && this.terminal) {
@@ -214,7 +215,8 @@ class FloatingModule {
const header = document.createElement('div');
header.className = 'floating-menu-header';
- header.innerHTML = `<span class="arrow ${el.collapsed ? 'collapsed' : ''}">▼</span> ${el.label}`;
+ // v4.5.67: Sanitize label — use escaped text to prevent XSS
+ header.innerHTML = `<span class="arrow ${el.collapsed ? 'collapsed' : ''}">▼</span> ${this._escapeHtml(el.label || '')}`;
const body = document.createElement('div');
body.className = 'floating-menu-body' + (el.collapsed ? '' : ' expanded');
@@ -266,8 +268,11 @@ class FloatingModule {
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
// `code`
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
- // [text](url)
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
+ // [text](url) — v4.5.67: sanitize URL to prevent javascript: scheme
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
+ const safeUrl = /^(https?:\/\/|mailto:|\/|#)/.test(url) ? url : '#';
+ return `<a href="${safeUrl}" target="_blank" rel="noopener">${text}</a>`;
+ });
// newlines to <br>
html = html.replace(/\n/g, '<br>');
return html;