aboutsummaryrefslogtreecommitdiff
path: root/modules/core/terminal.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-23 17:53:39 +0200
committerAgando <roduccomm@gmail.com>2026-10-23 17:53:39 +0200
commit2eec7a5c7ab9b724a718ece8b1ffc434677068ac (patch)
treea64817ed0aa938ecc2f75f252d6dd0d4c5587cd8 /modules/core/terminal.js
parent71d78fa055d00ed51a9067c441d562807ea95404 (diff)
fix(newos4): refuse shell composition, wc takes multiple paths (v4.6.58)
Packet 1 and 3 (silent wrong answers from a shell-less terminal). - detectComposition() in the security gate refuses ; && || | before dispatch. `;` used to fail loudly, but `&&`/`||` folded the right-hand side into the left command's argv (wc -l f && grep -c x f returned "42 42"), and a `|` arriving from the AI run_command tool bypassed splitPipe entirely and degraded the pattern to empty -- which matches every line. Operators inside quotes are untouched. - wc accepts multiple paths: one line per file plus a total, per-file errors for missing paths. `wc -c a b` used to print only `a` and exit 0. - help.html: "There is no shell" callout at the top of the command reference, plus the v4.6.57/58 changelog entry. Verified live on v4.6.58 by replaying the exact call sequence used by both paths: interactive `wc -c a b` -> "14 a / 10 b / 24 total"; `&&` and `;` refused; `echo "a && b"` still echoes; AI run_command semantics with `|` and `&&` refused; single command and multi-file wc still return real output.
Diffstat (limited to 'modules/core/terminal.js')
-rw-r--r--modules/core/terminal.js26
1 files changed, 26 insertions, 0 deletions
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index ad4b105..10608d1 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -176,7 +176,33 @@
const unknown = !known;
return { cmd, args, known, destructive, system, unknown, hasSuspicious };
}
+ // v4.6.58: this terminal has no shell. `;` at least failed loudly, but `&&`/`||`
+ // folded the right-hand side into the left command's argv, and a `|` arriving from
+ // the AI run_command tool (which bypasses splitPipe) degraded to an empty pattern
+ // matching every line. Both are silent wrong answers — refuse instead (packet §1).
+ detectComposition(input) {
+ const s = String(input || '');
+ let inSingle = false, inDouble = false;
+ for (let i = 0; i < s.length; i++) {
+ const ch = s[i];
+ if (ch === '"' && !inSingle) { inDouble = !inDouble; continue; }
+ if (ch === "'" && !inDouble) { inSingle = !inSingle; continue; }
+ if (inSingle || inDouble) continue;
+ if (ch === '&' && s[i + 1] === '&') return '&&';
+ if (ch === '|' && s[i + 1] === '|') return '||';
+ if (ch === '|') return '|';
+ if (ch === ';') return ';';
+ }
+ return null;
+ }
async checkAndRun(rawInput) {
+ const badOp = this.detectComposition(rawInput);
+ if (badOp) {
+ this.terminal.print('⚠️ No shell here — `' + badOp + '` is not interpreted. ' +
+ 'Run one command per line (the interactive terminal supports `cmd | cmd`; ' +
+ 'AI run_command does not).', 'error');
+ return;
+ }
const { cmd, args, known, destructive, system, unknown, hasSuspicious } = this._classify(rawInput);
// Level 3 Audit: just log and allow
if (this.level === 3) {