aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/critical-ext-bundle.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
committerAgando <roduccomm@gmail.com>2026-07-09 06:00:32 +0200
commitcf7461ffeb4f53f3f50c9c692a2e4fdcecf17e82 (patch)
treee7780cd41271381cf108c9c81d71a3cb65cfd555 /modules/ext/critical-ext-bundle.js
parentea4d09fb5ccba12ed774fcf7c913edfa1e6a851a (diff)
auto: 2026-07-09_06:00 changes
Diffstat (limited to 'modules/ext/critical-ext-bundle.js')
-rw-r--r--modules/ext/critical-ext-bundle.js315
1 files changed, 199 insertions, 116 deletions
diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js
index 3c5d54b..2230498 100644
--- a/modules/ext/critical-ext-bundle.js
+++ b/modules/ext/critical-ext-bundle.js
@@ -186,7 +186,18 @@ class ShellModule {
if (!rawInput.trim()) return;
this.markDirty();
this.printUserPrompt(rawInput);
-
+
+ // Save to command history (preserved from original executeCommand)
+ const cmdRaw = rawInput.trim();
+ if (this.history.length === 0 || this.history[this.history.length - 1] !== cmdRaw) {
+ this.history.push(cmdRaw);
+ }
+ if (this.history.length > this._historyCap) {
+ this.history = this.history.slice(-this._historyCap);
+ }
+ this.historyIndex = this.history.length;
+ this._saveCommandHistory();
+
// For simple single-word commands without special chars, skip shell parsing
if (/^[a-zA-Z0-9._\/-]+$/.test(rawInput.trim()) && !rawInput.includes(' ') && !rawInput.includes('$')) {
try {
@@ -196,19 +207,20 @@ class ShellModule {
return;
}
}
-
+
// Substitute variables in the raw input
const substituted = self._substituteVars(rawInput);
// Split on ; first (sequence)
const seqParts = self._splitSequence(substituted);
for (const seqPart of seqParts) {
if (!seqPart.trim()) continue;
- // Split on && and ||
+ // Split on && and || — operator now attached to the FOLLOWING segment
const chainParts = self._splitChain(seqPart.trim());
let lastOk = true;
for (const chainPart of chainParts) {
if (!chainPart.cmd.trim()) continue;
const { cmd: chainCmd, operator } = chainPart;
+ // Check the operator that PRECEDES this command
if (operator === '&&' && !lastOk) continue;
if (operator === '||' && lastOk) continue;
// Check for redirects
@@ -219,28 +231,40 @@ class ShellModule {
lastOk = redirectInfo.exitOk;
} else {
// Normal or piped command
- const pipeParts = self.terminal.splitPipe(chainCmd);
+ const pipeParts = this.splitPipe(chainCmd);
if (pipeParts.length > 1) {
- await self.terminal.executePipeChain(pipeParts);
+ await this.executePipeChain(pipeParts);
} else {
- // Single command
+ // Single command — check for shell built-ins first
const parts = chainCmd.split(/\s+/);
const cmd = parts[0].toLowerCase();
const args = parts.slice(1);
- try {
- await this.security.checkAndRun(chainCmd);
- } catch(e) {
- this.terminal && this.terminal.print ? this.terminal.print('Error: ' + e.message, 'error') : console.error(e);
+ if (self.commands[cmd]) {
+ try {
+ this.markDirty();
+ await self.commands[cmd](args);
+ lastOk = true;
+ } catch(e) {
+ this.print('Error: ' + e.message, 'error');
+ lastOk = false;
+ }
+ } else {
+ try {
+ await this.security.checkAndRun(chainCmd);
+ lastOk = true;
+ } catch(e) {
+ this.print('Error: ' + e.message, 'error');
+ lastOk = false;
+ }
}
}
- lastOk = true;
}
}
}
};
this.terminal._shellModule = this;
- // Modify pipe chain to read _pipeInput for commands that support it
+ // Modify pipe chain to add pipe-aware injection and security checks
const origPipeChain = this.terminal.executePipeChain.bind(this.terminal);
this.terminal.executePipeChain = async function(pipeParts) {
// Write pipe input from previous segment as temp file for next segment
@@ -251,6 +275,22 @@ class ShellModule {
const parts = cmdText.split(/\s+/);
const cmd = parts[0].toLowerCase();
let args = parts.slice(1);
+
+ // Security gate — replicate the original executePipeChain checks
+ const cls = this.security._classify(cmdText);
+ if (cls.destructive && this.security.level > 0) {
+ this.print(`⚠️ Security: Destructive command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error');
+ return;
+ }
+ if (cls.system && this.security.level > 0) {
+ this.print(`⚠️ Security: System command "${cmd}" blocked in pipe at level ${this.security.level}`, 'error');
+ return;
+ }
+ if (!cls.known && this.security.level >= 2) {
+ this.print(`⚠️ Security: Unknown command "${cmd}" blocked in pipe (paranoid mode)`, 'error');
+ return;
+ }
+
this._pipeInput = pipeInput;
if (pipeInput !== null) {
@@ -265,16 +305,17 @@ class ShellModule {
}
}
- if (i < pipeParts.length - 1) {
- pipeInput = await this.capturePipeOutput(cmd, args);
- } else {
- await this.dispatchCommand(cmd, args);
- }
-
- // Clean up temp file
- if (this.fs.readFile('__pipebuf__') !== null) {
- this.fs.root.content['__pipebuf__'] = undefined;
- delete this.fs.root.content['__pipebuf__'];
+ try {
+ if (i < pipeParts.length - 1) {
+ pipeInput = await this.capturePipeOutput(cmd, args);
+ } else {
+ await this.dispatchCommand(cmd, args);
+ }
+ } finally {
+ // Clean up temp file
+ if (this.fs.readFile('__pipebuf__') !== null) {
+ delete this.fs.root.content['__pipebuf__'];
+ }
}
}
this._pipeInput = null;
@@ -307,10 +348,12 @@ class ShellModule {
_splitChain(input) {
// Split on && and ||, preserving operator info
+ // Operator is attached to the FOLLOWING segment (the one it guards)
const parts = [];
let current = '';
let inQuote = false;
let quoteChar = '';
+ let pendingOp = null; // operator from previous split, applies to next segment
let i = 0;
while (i < input.length) {
const ch = input[i];
@@ -319,16 +362,18 @@ class ShellModule {
} else if (ch === quoteChar && inQuote) {
inQuote = false; quoteChar = ''; current += ch; i++;
} else if (!inQuote && input.substring(i, i+2) === '&&') {
- parts.push({ cmd: current.trim(), operator: '&&' });
+ parts.push({ cmd: current.trim(), operator: pendingOp });
+ pendingOp = '&&';
current = ''; i += 2;
} else if (!inQuote && input.substring(i, i+2) === '||') {
- parts.push({ cmd: current.trim(), operator: '||' });
+ parts.push({ cmd: current.trim(), operator: pendingOp });
+ pendingOp = '||';
current = ''; i += 2;
} else {
current += ch; i++;
}
}
- parts.push({ cmd: current.trim(), operator: null });
+ parts.push({ cmd: current.trim(), operator: pendingOp });
return parts;
}
@@ -351,7 +396,7 @@ class ShellModule {
inQuote = false; quoteChar = ''; current += ch; i++;
} else if (!inQuote) {
if (input.substring(i, i+2) === '>>') {
- if (current.trim()) parts.push(current.trim());
+ if (current.trim()) { parts.push(current.trim()); current = ''; }
hasRedirect = true;
redirectOp = '>>';
append = true;
@@ -366,7 +411,7 @@ class ShellModule {
if (i < input.length && (input[i] === '>' || input[i] === '<')) continue;
break;
} else if (input.substring(i, i+1) === '>') {
- if (current.trim()) parts.push(current.trim());
+ if (current.trim()) { parts.push(current.trim()); current = ''; }
hasRedirect = true;
redirectOp = '>';
append = false;
@@ -380,7 +425,7 @@ class ShellModule {
if (i < input.length && (input[i] === '>' || input[i] === '<')) continue;
break;
} else if (input.substring(i, i+1) === '<') {
- if (current.trim()) parts.push(current.trim());
+ if (current.trim()) { parts.push(current.trim()); current = ''; }
hasRedirect = true;
redirectOp = '<';
i += 1;
@@ -421,28 +466,38 @@ class ShellModule {
const inputContent = fs.readFile(redirectFile);
if (inputContent === null) {
this.terminal.print(`shell: ${redirectFile}: No such file`, 'error');
+ redirectInfo.exitOk = false;
return;
}
this.terminal._pipeInput = inputContent;
- // Execute the command with pipe input available
- const parts = cmdText.split(/\s+/);
- const cmd = parts[0].toLowerCase();
- const args = parts.slice(1);
- // If command reads files, inject pipe content as temp file
- if (this._isPipeAware(cmd)) {
- fs.writeFile('__pipebuf__', inputContent);
- const newArgs = [...args];
- if (!newArgs.some(a => !a.startsWith('-'))) {
- newArgs.push('__pipebuf__');
- }
- await this.terminal.dispatchCommand(cmd, newArgs);
- if (fs.readFile('__pipebuf__') !== null) {
- delete fs.root.content['__pipebuf__'];
+ try {
+ const parts = cmdText.split(/\s+/);
+ const cmd = parts[0].toLowerCase();
+ const args = parts.slice(1);
+ // If command reads files, inject pipe content as temp file
+ if (this._isPipeAware(cmd)) {
+ fs.writeFile('__pipebuf__', inputContent);
+ const newArgs = [...args];
+ if (!newArgs.some(a => !a.startsWith('-'))) {
+ newArgs.push('__pipebuf__');
+ }
+ try {
+ await this.terminal.dispatchCommand(cmd, newArgs);
+ } finally {
+ if (fs.readFile('__pipebuf__') !== null) {
+ delete fs.root.content['__pipebuf__'];
+ }
+ }
+ } else {
+ await this.terminal.dispatchCommand(cmd, args);
}
- } else {
- await this.terminal.dispatchCommand(cmd, args);
+ redirectInfo.exitOk = true;
+ } catch(e) {
+ this.terminal.print('Error: ' + e.message, 'error');
+ redirectInfo.exitOk = false;
+ } finally {
+ this.terminal._pipeInput = null;
}
- this.terminal._pipeInput = null;
return;
}
@@ -450,42 +505,57 @@ class ShellModule {
const originalPrint = this.terminal.print.bind(this.terminal);
let output = '';
this.terminal.print = (text, type) => { output += text + '\n'; };
+ let cmdOk = true;
- // Check for pipes within the command before redirect
- const pipeParts = this.terminal.splitPipe(cmdText);
- if (pipeParts.length > 1) {
- // Execute pipe chain manually to capture all output
- let pipeInput = null;
- for (let i = 0; i < pipeParts.length; i++) {
- const pText = pipeParts[i];
- const pParts = pText.split(/\s+/);
- const pCmd = pParts[0].toLowerCase();
- const pArgs = pParts.slice(1);
- if (pipeInput !== null) {
- fs.writeFile('__pipebuf__', pipeInput);
- }
- if (i < pipeParts.length - 1) {
- const captPrint = this.terminal.print.bind(this.terminal);
- let capt = '';
- this.terminal.print = (t, ty) => { capt += t + '\n'; };
- await this.terminal.dispatchCommand(pCmd, pArgs);
- this.terminal.print = captPrint;
- pipeInput = capt.trim();
- } else {
- await this.terminal.dispatchCommand(pCmd, pArgs);
- }
- if (fs.readFile('__pipebuf__') !== null) {
- delete fs.root.content['__pipebuf__'];
+ try {
+ // Check for pipes within the command before redirect
+ const pipeParts = this.terminal.splitPipe(cmdText);
+ if (pipeParts.length > 1) {
+ // Execute pipe chain manually to capture all output
+ let pipeInput = null;
+ for (let i = 0; i < pipeParts.length; i++) {
+ const pText = pipeParts[i];
+ const pParts = pText.split(/\s+/);
+ const pCmd = pParts[0].toLowerCase();
+ const pArgs = pParts.slice(1);
+ if (pipeInput !== null) {
+ fs.writeFile('__pipebuf__', pipeInput);
+ }
+ if (i < pipeParts.length - 1) {
+ const captPrint = this.terminal.print.bind(this.terminal);
+ let capt = '';
+ this.terminal.print = (t, ty) => { capt += t + '\n'; };
+ try {
+ await this.terminal.dispatchCommand(pCmd, pArgs);
+ } finally {
+ this.terminal.print = captPrint;
+ }
+ pipeInput = capt.trim();
+ } else {
+ await this.terminal.dispatchCommand(pCmd, pArgs);
+ }
+ if (fs.readFile('__pipebuf__') !== null) {
+ delete fs.root.content['__pipebuf__'];
+ }
}
+ } else {
+ const parts = cmdText.split(/\s+/);
+ const cmd = parts[0].toLowerCase();
+ const args = parts.slice(1);
+ await this.terminal.dispatchCommand(cmd, args);
}
- } else {
- const parts = cmdText.split(/\s+/);
- const cmd = parts[0].toLowerCase();
- const args = parts.slice(1);
- await this.terminal.dispatchCommand(cmd, args);
+ } catch(e) {
+ cmdOk = false;
+ // Restore print before showing the error
+ this.terminal.print = originalPrint;
+ this.terminal.print('Error: ' + e.message, 'error');
+ redirectInfo.exitOk = false;
+ return;
+ } finally {
+ // Always restore print — critical: without this, terminal is permanently broken
+ this.terminal.print = originalPrint;
}
- this.terminal.print = originalPrint;
const captured = output.trimEnd();
if (captured) {
@@ -503,6 +573,7 @@ class ShellModule {
this.terminal.print(`Redirected (empty) to ${redirectFile}`, 'success');
}
}
+ redirectInfo.exitOk = cmdOk;
}
_isPipeAware(cmd) {
@@ -622,54 +693,66 @@ class ShellModule {
const content = this.terminal.fs.readFile(path);
if (content === null) return this.terminal.print(`script: ${path}: No such file`, 'error');
this._scriptDepth++;
- this.terminal.print(`Running script: ${path}`, 'info');
- const lines = content.split('\n');
- let lineNum = 0;
- for (const rawLine of lines) {
- lineNum++;
- const line = rawLine.trim();
- if (!line || line.startsWith('#')) continue;
- // Run each line through the shell (by calling executeCommand directly)
- // We need to manually call the wrapped executeCommand
- this.terminal.print(`[${path}:${lineNum}] ${line}`, 'command-prompt');
- // Parse inline: handle pipe, redirect, chain, sequence
- const substituted = this._substituteVars(line);
- const seqParts = this._splitSequence(substituted);
- for (const seqPart of seqParts) {
- if (!seqPart.trim()) continue;
- const chainParts = this._splitChain(seqPart.trim());
- let lastOk = true;
- for (const chainPart of chainParts) {
- if (!chainPart.cmd.trim()) continue;
- if (chainPart.operator === '&&' && !lastOk) continue;
- if (chainPart.operator === '||' && lastOk) continue;
- const redirectInfo = this._extractRedirects(chainPart.cmd);
- if (redirectInfo.hasRedirect) {
- await this._executeWithRedirect(redirectInfo);
- lastOk = redirectInfo.exitOk;
- } else {
- const pipeParts = this.terminal.splitPipe(chainPart.cmd);
- if (pipeParts.length > 1) {
- await this.terminal.executePipeChain(pipeParts);
+ try {
+ this.terminal.print(`Running script: ${path}`, 'info');
+ const lines = content.split('\n');
+ let lineNum = 0;
+ for (const rawLine of lines) {
+ lineNum++;
+ const line = rawLine.trim();
+ if (!line || line.startsWith('#')) continue;
+ this.terminal.print(`[${path}:${lineNum}] ${line}`, 'command-prompt');
+ const substituted = this._substituteVars(line);
+ const seqParts = this._splitSequence(substituted);
+ for (const seqPart of seqParts) {
+ if (!seqPart.trim()) continue;
+ const chainParts = this._splitChain(seqPart.trim());
+ let lastOk = true;
+ for (const chainPart of chainParts) {
+ if (!chainPart.cmd.trim()) continue;
+ if (chainPart.operator === '&&' && !lastOk) continue;
+ if (chainPart.operator === '||' && lastOk) continue;
+ const redirectInfo = this._extractRedirects(chainPart.cmd);
+ if (redirectInfo.hasRedirect) {
+ await this._executeWithRedirect(redirectInfo);
+ lastOk = redirectInfo.exitOk;
} else {
- const parts = chainPart.cmd.split(/\s+/);
- const cmd = parts[0].toLowerCase();
- const args = parts.slice(1);
- // Check if it's our built-in command
- if (this.commands[cmd]) {
- this.terminal.markDirty();
- await this.commands[cmd](args);
+ const pipeParts = this.terminal.splitPipe(chainPart.cmd);
+ if (pipeParts.length > 1) {
+ await this.terminal.executePipeChain(pipeParts);
} else {
- await this.terminal.security.checkAndRun(chainPart.cmd);
+ const parts = chainPart.cmd.split(/\s+/);
+ const cmd = parts[0].toLowerCase();
+ const args = parts.slice(1);
+ // Check if it's a shell built-in command
+ if (this.commands[cmd]) {
+ try {
+ this.terminal.markDirty();
+ await this.commands[cmd](args);
+ lastOk = true;
+ } catch(e) {
+ this.terminal.print('Error: ' + e.message, 'error');
+ lastOk = false;
+ }
+ } else {
+ try {
+ await this.terminal.security.checkAndRun(chainPart.cmd);
+ lastOk = true;
+ } catch(e) {
+ this.terminal.print('Error: ' + e.message, 'error');
+ lastOk = false;
+ }
+ }
}
}
- lastOk = true;
}
}
}
+ this.terminal.print(`Script ${path} completed (${lineNum} lines)`, 'success');
+ } finally {
+ // Always decrement — even on error or thrown exception
+ this._scriptDepth--;
}
- this._scriptDepth--;
- this.terminal.print(`Script ${path} completed (${lineNum} lines)`, 'success');
}
}
if (typeof window.webx11 !== 'undefined' && window.webx11) {