wolverine-ai 4.5.0 → 4.5.1

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": "wolverine-ai",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -477,6 +477,13 @@ const BLOCKED_COMMANDS = [
477
477
  /\bnpm\s+publish\b/i, // no accidental publishes
478
478
  /\bcurl\b.*\|\s*bash/i, // pipe to bash
479
479
  /\beval\s*\(/i,
480
+ /wget.*\|\s*sh/i, // wget pipe to shell
481
+ /curl.*\$\(/i, // curl data exfiltration via command substitution
482
+ /cat\s+\.env/i, // read secrets via bash
483
+ />\s*src\//i, // redirect write to src/
484
+ /cp\s+.*\s+src\//i, // copy into src/
485
+ /tee\s+.*src\//i, // tee into src/
486
+ /mv\s+.*\s+src\//i, // move into src/
480
487
  ];
481
488
 
482
489
  class AgentEngine {
@@ -885,7 +892,7 @@ class AgentEngine {
885
892
 
886
893
  try {
887
894
  this.sandbox.resolve(fullPath);
888
- const content = fs.readFileSync(fullPath, "utf-8");
895
+ const content = this.sandbox.readFile(fullPath);
889
896
  const lines = content.split("\n");
890
897
  const relPath = path.relative(this.cwd, fullPath).replace(/\\/g, "/");
891
898
 
@@ -946,7 +953,7 @@ class AgentEngine {
946
953
 
947
954
  _gitLog(args) {
948
955
  const count = args.count || 10;
949
- const fileFilter = args.file ? ` -- ${args.file}` : "";
956
+ const fileFilter = args.file ? ` -- "${args.file}"` : "";
950
957
  try {
951
958
  const output = execSync(
952
959
  `git log --oneline --no-decorate -n ${count}${fileFilter}`,
@@ -960,8 +967,8 @@ class AgentEngine {
960
967
  }
961
968
 
962
969
  _gitDiff(args) {
963
- const ref = args.ref || "";
964
- const fileFilter = args.file ? ` -- ${args.file}` : "";
970
+ const ref = args.ref ? `"${args.ref}"` : "";
971
+ const fileFilter = args.file ? ` -- "${args.file}"` : "";
965
972
  try {
966
973
  const output = execSync(
967
974
  `git diff ${ref}${fileFilter}`,
@@ -984,6 +991,30 @@ class AgentEngine {
984
991
  return;
985
992
  }
986
993
 
994
+ // SSRF protection: block private/internal IPs
995
+ try {
996
+ const parsedUrl = new (require("url").URL)(url);
997
+ const hostname = parsedUrl.hostname;
998
+ const privatePatterns = [
999
+ /^127\./,
1000
+ /^localhost$/i,
1001
+ /^169\.254\.169\.254$/,
1002
+ /^100\.100\.100\.200$/,
1003
+ /^10\./,
1004
+ /^172\.(1[6-9]|2\d|3[01])\./,
1005
+ /^192\.168\./,
1006
+ /^fd[0-9a-f]{2}:/i,
1007
+ /^::1$/,
1008
+ ];
1009
+ if (privatePatterns.some(p => p.test(hostname))) {
1010
+ resolve({ content: `BLOCKED: Cannot fetch private/internal address "${hostname}"` });
1011
+ return;
1012
+ }
1013
+ } catch (e) {
1014
+ resolve({ content: `Error parsing URL: ${e.message}` });
1015
+ return;
1016
+ }
1017
+
987
1018
  const client = url.startsWith("https") ? https : http;
988
1019
  const req = client.get(url, { timeout: 10000 }, (res) => {
989
1020
  let data = "";
@@ -1020,6 +1051,7 @@ class AgentEngine {
1020
1051
  _listDir(args) {
1021
1052
  const dirPath = path.resolve(this.cwd, args.path || ".");
1022
1053
  try {
1054
+ this.sandbox.resolve(dirPath);
1023
1055
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
1024
1056
  const lines = entries.map(e => {
1025
1057
  try {
@@ -1066,15 +1098,18 @@ class AgentEngine {
1066
1098
 
1067
1099
  _checkEnv(args) {
1068
1100
  const { redact } = require("../security/secret-redactor");
1101
+ const isSecretKey = (k) => /KEY|SECRET|TOKEN|PASSWORD|AUTH|CREDENTIAL/i.test(k);
1069
1102
  if (args.variable) {
1070
1103
  const val = process.env[args.variable];
1071
- const display = val ? redact(val) : "(not set)";
1104
+ if (!val) return { content: `${args.variable}=(not set)` };
1105
+ const display = isSecretKey(args.variable) ? "SET (redacted)" : redact(val);
1072
1106
  return { content: `${args.variable}=${display}` };
1073
1107
  }
1074
1108
  // List all env vars with redacted values
1075
1109
  const keys = Object.keys(process.env).sort();
1076
1110
  const lines = keys.map(k => {
1077
1111
  const val = process.env[k];
1112
+ if (isSecretKey(k)) return `${k}=${val ? "SET (redacted)" : "(not set)"}`;
1078
1113
  return `${k}=${val && val.length > 50 ? "(set, " + val.length + " chars)" : redact(val || "")}`;
1079
1114
  });
1080
1115
  return { content: lines.join("\n") };
@@ -1101,6 +1136,9 @@ class AgentEngine {
1101
1136
  if (!upper.startsWith("SELECT") && !upper.startsWith("PRAGMA")) {
1102
1137
  return { content: "BLOCKED: inspect_db only allows SELECT/PRAGMA. Use run_db_fix for writes." };
1103
1138
  }
1139
+ if (args.sql.includes(";")) {
1140
+ return { content: "BLOCKED: inspect_db does not allow stacked queries (multiple statements separated by ';')." };
1141
+ }
1104
1142
  const rows = db.prepare(args.sql).all();
1105
1143
  result = JSON.stringify(rows.slice(0, 50), null, 2);
1106
1144
  if (rows.length > 50) result += `\n... (${rows.length} total rows, showing first 50)`;
@@ -1129,7 +1167,9 @@ class AgentEngine {
1129
1167
 
1130
1168
  // Backup the DB file first
1131
1169
  const backupPath = dbPath + ".wolverine-backup";
1132
- fs.copyFileSync(dbPath, backupPath);
1170
+ if (fs.existsSync(dbPath)) {
1171
+ fs.copyFileSync(dbPath, backupPath);
1172
+ }
1133
1173
 
1134
1174
  const db = new Database(dbPath);
1135
1175
 
@@ -1258,7 +1298,7 @@ class AgentEngine {
1258
1298
  }
1259
1299
 
1260
1300
  _checkLogs(args) {
1261
- const lines = args.lines || 50;
1301
+ const lines = Math.max(1, Math.min(parseInt(args.lines, 10) || 50, 1000));
1262
1302
  const filter = args.filter || "";
1263
1303
  try {
1264
1304
  let cmd;
@@ -1291,8 +1331,9 @@ class AgentEngine {
1291
1331
  if (args.host) {
1292
1332
  try {
1293
1333
  const dns = require("dns");
1294
- const addresses = execSync(`node -e "require('dns').resolve('${args.host.replace(/'/g, "")}', (e,a) => console.log(e ? 'FAIL:'+e.code : a.join(',')))"`, { encoding: "utf-8", timeout: 5000 }).trim();
1295
- results.push(`DNS ${args.host}: ${addresses}`);
1334
+ const safeHost = args.host.replace(/[^a-zA-Z0-9.:\/-_]/g, "");
1335
+ const addresses = execSync(`node -e "require('dns').resolve('${safeHost.replace(/'/g, "")}', (e,a) => console.log(e ? 'FAIL:'+e.code : a.join(',')))"`, { encoding: "utf-8", timeout: 5000 }).trim();
1336
+ results.push(`DNS ${safeHost}: ${addresses}`);
1296
1337
  } catch (e) { results.push(`DNS ${args.host}: FAILED — ${e.message}`); }
1297
1338
  }
1298
1339
  // Port check
@@ -1308,8 +1349,9 @@ class AgentEngine {
1308
1349
  // URL reachability
1309
1350
  if (args.url) {
1310
1351
  try {
1311
- const urlResult = execSync(`node -e "require('${args.url.startsWith('https') ? 'https' : 'http'}').get('${args.url.replace(/'/g, "")}', r => { console.log(r.statusCode); r.resume(); }).on('error', e => console.log('FAIL:'+e.code))"`, { encoding: "utf-8", timeout: 10000 }).trim();
1312
- results.push(`URL ${args.url}: ${urlResult}`);
1352
+ const safeUrl = args.url.replace(/[^a-zA-Z0-9.:\/-_]/g, "");
1353
+ const urlResult = execSync(`node -e "require('${safeUrl.startsWith('https') ? 'https' : 'http'}').get('${safeUrl.replace(/'/g, "")}', r => { console.log(r.statusCode); r.resume(); }).on('error', e => console.log('FAIL:'+e.code))"`, { encoding: "utf-8", timeout: 10000 }).trim();
1354
+ results.push(`URL ${safeUrl}: ${urlResult}`);
1313
1355
  } catch (e) { results.push(`URL ${args.url}: FAILED — ${e.message}`); }
1314
1356
  }
1315
1357
  if (results.length === 0) results.push("Provide host, port, or url to check.");
@@ -1358,7 +1400,15 @@ class AgentEngine {
1358
1400
  if (fs.existsSync(binDir)) {
1359
1401
  for (const bin of fs.readdirSync(binDir)) {
1360
1402
  const target = path.join(binDir, bin);
1361
- try { fs.readlinkSync(target); } catch { brokenBins++; }
1403
+ try {
1404
+ if (process.platform === "win32") {
1405
+ // Windows uses .cmd shims instead of symlinks
1406
+ const cmdFile = target.endsWith(".cmd") ? target : target + ".cmd";
1407
+ if (!fs.existsSync(cmdFile) && !fs.existsSync(target)) brokenBins++;
1408
+ } else {
1409
+ fs.readlinkSync(target);
1410
+ }
1411
+ } catch { brokenBins++; }
1362
1412
  }
1363
1413
  }
1364
1414
  const rec = missing.length > 5 || broken.length > 3 ? "rm -rf node_modules && npm install" : missing.length > 0 ? "npm install" : "ok";
@@ -1475,7 +1525,10 @@ class AgentEngine {
1475
1525
  try { execSync(`rm -rf "${t.path.replace("~", os.homedir())}"`, { timeout: 10000 }); } catch {}
1476
1526
  }
1477
1527
  }
1478
- const diskFree = Math.round(parseInt(execSync("df -m . | tail -1 | awk '{print $4}'", { encoding: "utf-8", cwd: this.cwd, timeout: 3000 }).trim() || "0", 10));
1528
+ let diskFree;
1529
+ try {
1530
+ diskFree = Math.round(parseInt(execSync("df -m . | tail -1 | awk '{print $4}'", { encoding: "utf-8", cwd: this.cwd, timeout: 3000 }).trim() || "0", 10));
1531
+ } catch { diskFree = "unknown"; }
1479
1532
  const lines = [
1480
1533
  `Disk free: ${diskFree}MB`,
1481
1534
  `Reclaimable: ${reclaimable}MB (${targets.length} targets)`,
@@ -1653,7 +1706,7 @@ class AgentEngine {
1653
1706
  function _simplePrompt(cwd, primaryFile) {
1654
1707
  return `You are Wolverine, a Node.js server repair agent. Fix the error using minimal changes.
1655
1708
 
1656
- TOOLS: read_file, write_file, edit_file, glob_files, grep_code, bash_exec, done
1709
+ TOOLS: read_file, write_file, edit_file, glob_files, grep_code, bash_exec, done + 24 more diagnostic tools available
1657
1710
  RULES: Read the file before editing. Use edit_file for targeted fixes. Call done when finished. Use multiple tools at once when independent.
1658
1711
  ${primaryFile ? `File: ${primaryFile}` : ""}
1659
1712
  Project: ${cwd}`;
@@ -1669,7 +1722,7 @@ CRITICAL: Act fast. You have limited turns. Fix immediately when the solution is
1669
1722
 
1670
1723
  For maximum efficiency, invoke multiple independent tools simultaneously rather than sequentially.
1671
1724
 
1672
- TOOLS: read_file, write_file, edit_file, glob_files, grep_code, list_dir, move_file, bash_exec, git_log, git_diff, inspect_db, run_db_fix, check_port, check_env, audit_deps, check_migration, web_fetch, done
1725
+ TOOLS: read_file, write_file, edit_file, glob_files, grep_code, list_dir, move_file, bash_exec, git_log, git_diff, inspect_db, run_db_fix, check_port, check_env, audit_deps, check_migration, web_fetch, check_memory, list_processes, check_logs, restart_service, check_network, inspect_env, verify_node_modules, inspect_certificate, inspect_cache, disk_cleanup, check_file_descriptors, check_event_loop, check_websocket, done
1673
1726
 
1674
1727
  FAST FIXES (act immediately, don't investigate):
1675
1728
  - Cannot find module 'X' → bash_exec: npm install X → done