nothumanallowed 16.0.38 → 16.0.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.38",
3
+ "version": "16.0.40",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '16.0.38';
8
+ export const VERSION = '16.0.40';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -229,6 +229,17 @@ class SandboxManager {
229
229
  }
230
230
  } catch {}
231
231
 
232
+ // Pre-flight: repair unsafe err.X / error.X access in route handlers.
233
+ // LLMs write `res.send(err.stack)` without null-checking err → 500 on
234
+ // every request when error middleware signature is wrong (3 args not 4).
235
+ try {
236
+ const errRepaired = _repairUnsafeErrAccess(projectDir);
237
+ if (errRepaired.length > 0) {
238
+ const fileList = errRepaired.slice(0, 5).map(r => `${r.file} (${r.edits} fixes)`).join(', ');
239
+ emit({ type: 'status', msg: `Pre-flight: null-checked ${errRepaired.reduce((s, r) => s + r.edits, 0)} unsafe err/error accesses in ${errRepaired.length} file${errRepaired.length === 1 ? '' : 's'} → ${fileList}${errRepaired.length > 5 ? '...' : ''}` });
240
+ }
241
+ } catch {}
242
+
232
243
  // ── Phase 2: Dependencies (pre-scan + batch install) ──────────────────
233
244
  // Pre-scan the project source files for require()/import statements and
234
245
  // diff against package.json + node_modules. Install everything missing in
@@ -3611,6 +3622,93 @@ function _placeholderContent(kind, refPath) {
3611
3622
  * Creates missing directories + empty JSON placeholders so the app can boot
3612
3623
  * instead of crashing with ENOENT on first write.
3613
3624
  */
3625
+ /**
3626
+ * Auto-repair unsafe `err.X` and similar null-deref patterns in route handlers.
3627
+ * The most common LLM-generated bug: error middleware that does `res.send(err.stack)`
3628
+ * but `err` is undefined because the middleware signature is wrong (only 3 args
3629
+ * instead of 4). Result: 500 on EVERY request including static files.
3630
+ *
3631
+ * Pattern fixed:
3632
+ * err.stack → (err && err.stack) if not already null-checked
3633
+ * err.message → (err && err.message) same
3634
+ * error.stack → (error && error.stack) same
3635
+ * error.message → (error && error.message) same
3636
+ *
3637
+ * Skips matches already guarded (`err && err.stack`, `err?.stack`, `err?.message`).
3638
+ */
3639
+ export function _repairUnsafeErrAccess(projectDir) {
3640
+ const repaired = [];
3641
+ const exts = new Set(['.js', '.mjs', '.cjs']);
3642
+ const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next', 'public']);
3643
+ // Match `IDENT.PROP` where PROP is stack|message and IDENT is err|error|e.
3644
+ // Negative lookbehind: skip if preceded by `&&`, `||`, `?.`, `(`, `:`, `,`.
3645
+ // Use a global regex and inspect context manually.
3646
+ const targetIdents = ['err', 'error', 'e'];
3647
+ const targetProps = ['stack', 'message', 'code', 'statusCode', 'name'];
3648
+
3649
+ const stack = [projectDir];
3650
+ while (stack.length) {
3651
+ const cur = stack.pop();
3652
+ let entries;
3653
+ try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; }
3654
+ for (const ent of entries) {
3655
+ if (skipDirs.has(ent.name) || ent.name.startsWith('.')) continue;
3656
+ const abs = path.join(cur, ent.name);
3657
+ if (ent.isDirectory()) { stack.push(abs); continue; }
3658
+ if (!exts.has(path.extname(ent.name))) continue;
3659
+ let content;
3660
+ try { content = fs.readFileSync(abs, 'utf-8'); } catch { continue; }
3661
+
3662
+ let changed = content;
3663
+ const edits = [];
3664
+
3665
+ for (const ident of targetIdents) {
3666
+ for (const prop of targetProps) {
3667
+ // Match unsafe access: `ident.prop` not preceded by `&&`, `?`, `||`
3668
+ // Use a simpler regex + manual filter for surrounding context
3669
+ const re = new RegExp(`\\b${ident}\\.${prop}\\b`, 'g');
3670
+ changed = changed.replace(re, (match, offset, str) => {
3671
+ // Check context preceding this match
3672
+ const before = str.slice(Math.max(0, offset - 40), offset);
3673
+ // Already guarded patterns — skip
3674
+ if (/&&\s*$/.test(before)) return match;
3675
+ if (/\?\.$/.test(before.slice(-2))) return match;
3676
+ if (/\?\s*$/.test(before)) return match;
3677
+ if (/\|\|\s*$/.test(before)) return match;
3678
+ // Inside object literal key (e.g., `{ stack: err.stack }`) is harmless
3679
+ // when it's the value. We still wrap because the assignment crashes too.
3680
+ // Inside a string literal — skip
3681
+ const lineStart = str.lastIndexOf('\n', offset) + 1;
3682
+ const lineUpTo = str.slice(lineStart, offset);
3683
+ const singleQuotes = (lineUpTo.match(/(?<!\\)'/g) || []).length;
3684
+ const doubleQuotes = (lineUpTo.match(/(?<!\\)"/g) || []).length;
3685
+ const backticks = (lineUpTo.match(/(?<!\\)`/g) || []).length;
3686
+ if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0 || backticks % 2 !== 0) return match;
3687
+ // Inside a comment — skip
3688
+ if (/\/\/[^\n]*$/.test(lineUpTo)) return match;
3689
+ // Already inside an existing parens guard like `(err && err.stack)` — skip
3690
+ const wider = str.slice(Math.max(0, offset - 60), offset);
3691
+ if (new RegExp(`\\b${ident}\\s*&&\\s*$`).test(wider)) return match;
3692
+ // Wrap in null-check
3693
+ edits.push(`${ident}.${prop} → (${ident} && ${ident}.${prop})`);
3694
+ return `(${ident} && ${ident}.${prop})`;
3695
+ });
3696
+ }
3697
+ }
3698
+
3699
+ if (changed !== content && edits.length > 0) {
3700
+ try {
3701
+ fs.writeFileSync(abs + '.before-err-repair-' + Date.now(), content, 'utf-8');
3702
+ fs.writeFileSync(abs, changed, 'utf-8');
3703
+ const rel = path.relative(projectDir, abs).replace(/\\/g, '/');
3704
+ repaired.push({ file: rel, edits: edits.length, samples: edits.slice(0, 3) });
3705
+ } catch {}
3706
+ }
3707
+ }
3708
+ }
3709
+ return repaired;
3710
+ }
3711
+
3614
3712
  export function _detectMissingDataFiles(projectDir) {
3615
3713
  const created = [];
3616
3714
  const exts = new Set(['.js', '.mjs', '.cjs', '.ts']);
@@ -3674,6 +3772,13 @@ export function autoRepairProject(projectName) {
3674
3772
  repairs.push({ file: f, kind: 'missing-data-file', source: 'code-reference' });
3675
3773
  }
3676
3774
 
3775
+ // Phase 0.5: null-check unsafe err.X access in route handlers
3776
+ const errFixed = _repairUnsafeErrAccess(dir);
3777
+ for (const r of errFixed) {
3778
+ filesRepaired.add(r.file);
3779
+ repairs.push({ file: r.file, kind: 'unsafe-err-access', edits: r.edits });
3780
+ }
3781
+
3677
3782
  // Walk every HTML file in the project
3678
3783
  const stack = [dir];
3679
3784
  const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next']);
@@ -4093,6 +4198,56 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
4093
4198
  ` } catch(e) {}`,
4094
4199
  ` return _origEnd.apply(this, arguments);`,
4095
4200
  `};`,
4201
+ `// Monkey-patch Express to neutralize next(falsy) — common LLM bug where`,
4202
+ `// middleware calls next(err) with err === undefined for EVERY request,`,
4203
+ `// causing 500 on all routes including static files. Express treats any`,
4204
+ `// truthy first arg as an error; if it's falsy (null/undefined/0/''),`,
4205
+ `// it's semantically equivalent to next() per Express docs. We rewrite.`,
4206
+ `(function() {`,
4207
+ ` const Module = require('module');`,
4208
+ ` const _origLoad = Module._load;`,
4209
+ ` function wrapMiddleware(fn) {`,
4210
+ ` if (typeof fn !== 'function' || fn.length !== 3) return fn;`,
4211
+ ` const wrapped = function(req, res, next) {`,
4212
+ ` const safeNext = function(err) {`,
4213
+ ` if (err === undefined || err === null || err === false || err === 0) return next();`,
4214
+ ` return next(err);`,
4215
+ ` };`,
4216
+ ` try { return fn.call(this, req, res, safeNext); }`,
4217
+ ` catch (e) { return next(e); }`,
4218
+ ` };`,
4219
+ ` wrapped._nhaWrapped = true;`,
4220
+ ` return wrapped;`,
4221
+ ` }`,
4222
+ ` function patchExpressApp(express) {`,
4223
+ ` if (!express || express._nhaPatched) return express;`,
4224
+ ` try {`,
4225
+ ` // express() returns an app; patch app.use to wrap middleware`,
4226
+ ` const _origExpress = express;`,
4227
+ ` const factory = function(...args) {`,
4228
+ ` const app = _origExpress.apply(this, args);`,
4229
+ ` if (app && typeof app.use === 'function' && !app._nhaUseWrapped) {`,
4230
+ ` const _origUse = app.use;`,
4231
+ ` app.use = function(...uArgs) {`,
4232
+ ` const mapped = uArgs.map(a => (typeof a === 'function' && a.length === 3 && !a._nhaWrapped) ? wrapMiddleware(a) : a);`,
4233
+ ` return _origUse.apply(this, mapped);`,
4234
+ ` };`,
4235
+ ` app._nhaUseWrapped = true;`,
4236
+ ` }`,
4237
+ ` return app;`,
4238
+ ` };`,
4239
+ ` // Preserve static props: Router, json(), urlencoded(), etc.`,
4240
+ ` Object.assign(factory, _origExpress);`,
4241
+ ` factory._nhaPatched = true;`,
4242
+ ` return factory;`,
4243
+ ` } catch { return express; }`,
4244
+ ` }`,
4245
+ ` Module._load = function(name, parent, isMain) {`,
4246
+ ` const result = _origLoad.call(this, name, parent, isMain);`,
4247
+ ` if (name === 'express') return patchExpressApp(result);`,
4248
+ ` return result;`,
4249
+ ` };`,
4250
+ `})();`,
4096
4251
  `// Strip headers that break sandbox iframe preview:`,
4097
4252
  `// - X-Frame-Options: blocks iframe embedding entirely`,
4098
4253
  `// - X-Content-Type-Options: nosniff: when LLM references /js/*.js files`,