aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-fs.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
commit9ae724e87a6471ce503783615ef6083d991531ce (patch)
treeda5523a5c8d3550c4236ead87cf878067a9cedbe /modules/core/module-fs.js
parent83e64b88910c952c7469cee57cc5c5a8a07ad902 (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/module-fs.js')
-rw-r--r--modules/core/module-fs.js48
1 files changed, 34 insertions, 14 deletions
diff --git a/modules/core/module-fs.js b/modules/core/module-fs.js
index 03c3733..f93553a 100644
--- a/modules/core/module-fs.js
+++ b/modules/core/module-fs.js
@@ -438,26 +438,46 @@ class FileSystemModule {
this.commands['wc'] = (args) => {
// 2026-10-21: accept -l / -w / -c (previously ANY flag was read as a filename,
// so `wc -l file` answered "wc: -l: No such file").
+ // v4.6.58: multiple files. `wc -c a b` used to print only `a` and exit clean —
+ // a silent false negative (packet §1).
const want = { l: false, w: false, c: false };
- let file = null;
+ const files = [];
(args || []).forEach(a => {
if (/^-[lwc]+$/.test(a)) a.slice(1).split('').forEach(k => { want[k] = true; });
else if (/^-[a-zA-Z]+$/.test(a)) { /* ignore other flags */ }
- else if (!file) file = a;
+ else files.push(a);
});
- if (!file) return this.terminal.print('Usage: wc [-l] [-w] [-c] <filename>', 'warning');
- const content = this.terminal.fs.readFile(file);
- if (content === null || content === undefined) return this.terminal.print('wc: ' + file + ': No such file', 'error');
- const lines = content.split('\n').length;
- const words = content.split(/\s+/).filter(w => w).length;
- const chars = content.length;
+ if (!files.length) return this.terminal.print('Usage: wc [-l] [-w] [-c] <filename> [...]', 'warning');
const anyFlag = want.l || want.w || want.c;
- const out = [];
- if (want.l || !anyFlag) out.push(lines);
- if (want.w || !anyFlag) out.push(words);
- if (want.c || !anyFlag) out.push(chars);
- if (anyFlag && !(want.l && want.w && want.c)) out.push(file);
- this.terminal.print(out.join(' '));
+ const rows = [];
+ let total = { l: 0, w: 0, c: 0 };
+ files.forEach(file => {
+ const content = this.terminal.fs.readFile(file);
+ if (content === null || content === undefined) {
+ this.terminal.print('wc: ' + file + ': No such file', 'error');
+ return;
+ }
+ const r = {
+ file: file,
+ l: content.split('\n').length,
+ w: content.split(/\s+/).filter(w => w).length,
+ c: content.length
+ };
+ total.l += r.l; total.w += r.w; total.c += r.c;
+ rows.push(r);
+ });
+ if (!rows.length) return;
+ const multi = rows.length > 1;
+ const fmt = (r, name) => {
+ const out = [];
+ if (want.l || !anyFlag) out.push(r.l);
+ if (want.w || !anyFlag) out.push(r.w);
+ if (want.c || !anyFlag) out.push(r.c);
+ if (multi || (anyFlag && !(want.l && want.w && want.c))) out.push(name);
+ return out.join(' ');
+ };
+ rows.forEach(r => this.terminal.print(fmt(r, r.file)));
+ if (multi) this.terminal.print(fmt(total, 'total'));
};
this.commands['touch'] = (args) => {
if(!args[0]) return this.terminal.print('Usage: touch <filename>', 'warning');