@ngockhoale/ukit 3.4.18 → 3.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.19 - 2026-10-06
6
+
7
+ **Create-scope guard: more read-only shell false positives removed (found by replaying 4vn's real blocked commands).**
8
+ `2>/dev/null;` / `2>/dev/null|` (operator glued to the redirect target) was read as a write to a file
9
+ named `/dev/null;`; commands after shell keywords (`for ...; do jq ...`, `then`, `else`, `while`) skipped the
10
+ read-only interpreter mask. Real writes (`>src/a.js;`, write APIs) still block. Test 14 in createScopeGuard.
11
+
5
12
  ## 3.4.18 - 2026-10-06
6
13
 
7
14
  **Stops the token-burning respawn loop at its source.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.18",
3
+ "version": "3.4.19",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -407,7 +407,7 @@ const SCRIPT_WRITE_API = new RegExp([
407
407
  function firstWord(text) {
408
408
  const words = text.trim().split(/\s+/);
409
409
  let i = 0;
410
- while (i < words.length && (/^[A-Za-z_]\w*=/.test(words[i]) || /^(?:sudo|time|nohup|command|exec)$/.test(words[i]))) i += 1;
410
+ while (i < words.length && (/^[A-Za-z_]\w*=/.test(words[i]) || /^(?:sudo|time|nohup|command|exec|do|then|else|elif|if|while|until|!|\{|\()$/.test(words[i]))) i += 1;
411
411
  return (words[i] || '').replace(/^.*\//, '');
412
412
  }
413
413
  function splitShellSegments(command) {
@@ -488,7 +488,8 @@ function shellWriteHit(rawCommand) {
488
488
  // Redirect (optionally fd-numbered: `1>f`, `3>f`, `&>f`, `>&f`) to anything but /dev/null.
489
489
  // fd duplication (`2>&1`, `>&2`, `>&-`) is excluded by the `(?!&)` target shape.
490
490
  for (const m of command.matchAll(/(^|[^>])&?>{1,2}\s*(?:&(?![0-9-]))?((?!&)\S+)/g)) {
491
- if (m[2] !== '/dev/null') return true;
491
+ // `2>/dev/null;` / `2>/dev/null|` / `2>/dev/null)` glue the shell operator onto the target token.
492
+ if (!['/dev/null', '/dev/stdout', '/dev/stderr'].includes(m[2].replace(/[;&|)]+$/, ''))) return true;
492
493
  }
493
494
  return SHELL_WRITE_PATTERNS.some((re) => re.test(command));
494
495
  }