@wrongstack/core 0.277.0 → 0.277.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.
@@ -1,7 +1,7 @@
1
1
  import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
2
2
  import * as fs2 from 'fs';
3
3
  import * as fs from 'fs/promises';
4
- import * as path3 from 'path';
4
+ import * as path from 'path';
5
5
 
6
6
  // src/security/secret-scrubber.ts
7
7
  var PATTERNS = [
@@ -192,9 +192,9 @@ var DefaultSecretScrubber = class {
192
192
  }
193
193
  };
194
194
  async function atomicWrite(targetPath, content, opts = {}) {
195
- const dir = path3.dirname(targetPath);
195
+ const dir = path.dirname(targetPath);
196
196
  await fs.mkdir(dir, { recursive: true });
197
- const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
197
+ const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
198
198
  try {
199
199
  if (typeof content === "string") {
200
200
  await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -748,7 +748,7 @@ var DefaultSecretVault = class {
748
748
  const oldVersion = this._keyVersion;
749
749
  const newKey = randomBytes(KEY_BYTES);
750
750
  const newVersion = oldVersion + 1;
751
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
751
+ fs2.mkdirSync(path.dirname(this.keyFile), { recursive: true });
752
752
  const passphrase = getVaultPassphrase();
753
753
  if (passphrase) {
754
754
  fs2.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
@@ -832,7 +832,7 @@ var DefaultSecretVault = class {
832
832
  } catch (err) {
833
833
  if (err.code !== "ENOENT") throw err;
834
834
  }
835
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
835
+ fs2.mkdirSync(path.dirname(this.keyFile), { recursive: true });
836
836
  const key = randomBytes(KEY_BYTES);
837
837
  const passphrase = getVaultPassphrase();
838
838
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -931,7 +931,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
931
931
  }
932
932
  const merged = deepMerge(current, patch ?? {});
933
933
  const encrypted = encryptConfigSecrets(merged, vault);
934
- await fs.mkdir(path3.dirname(configPath), { recursive: true });
934
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
935
935
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
936
936
  await restrictFilePermissions(configPath);
937
937
  }
@@ -1222,19 +1222,51 @@ var LruCache = class {
1222
1222
  return this.store.size;
1223
1223
  }
1224
1224
  };
1225
- var DESTRUCTIVE_BASH_PATTERNS = [
1226
- /\bgit\s+(?:clean\s+-[^\s]*[xdf]|reset\s+--hard)\b/i,
1227
- /\b(?:drop|truncate)\s+(?:table|database|schema)\b/i,
1228
- /\bdelete\s+from\b/i,
1229
- /\b(?:mkfs|format|diskpart|shutdown|reboot)\b/i,
1230
- /\bchmod\s+-R\s+777\b/i,
1231
- /\bchown\s+-R\b/i,
1232
- /\b(?:curl|wget)\b.*\|\s*(?:sh|bash|zsh|pwsh|powershell)\b/i,
1233
- /\b(?:powershell|pwsh)\b.*(?:-encodedcommand|-enc)\b/i,
1234
- /:\(\)\s*\{\s*:\|:&\s*}\s*;/
1225
+ var CATASTROPHIC_PATTERNS = [
1226
+ /\b(?:mkfs(?:\.[a-z0-9]+)?|mke2fs|newfs)\b/i,
1227
+ // make a filesystem — wipes a partition
1228
+ /\bformat\s+[A-Za-z]:/i,
1229
+ // format C: — wipes a Windows volume
1230
+ /\bdiskpart\b/i,
1231
+ // Windows partition editor
1232
+ /\bdd\b[^|]*\bof=(?:\/dev\/|\\\\[.?]\\)/i,
1233
+ // dd writing straight to a raw device
1234
+ />\s*\/dev\/(?:sd|hd|nvme|disk|mapper|vd)/i,
1235
+ // redirect into a raw block device
1236
+ /:\(\)\s*\{\s*:\|:&\s*\}\s*;/
1237
+ // classic fork bomb
1235
1238
  ];
1236
- var PROJECT_ESCAPE_PATTERN = /(?:^|[\s"'])\.\.(?:[\\/]|$)/;
1237
- var ABSOLUTE_PATH_PATTERN = /(?:^|[\s"'])(?:~[\\/]|\/[A-Za-z0-9_.-]|[A-Za-z]:[\\/])/;
1239
+ var CATASTROPHIC_POSIX_ROOTS = /* @__PURE__ */ new Set([
1240
+ "/etc",
1241
+ "/usr",
1242
+ "/bin",
1243
+ "/sbin",
1244
+ "/lib",
1245
+ "/lib64",
1246
+ "/var",
1247
+ "/boot",
1248
+ "/dev",
1249
+ "/sys",
1250
+ "/proc",
1251
+ "/opt",
1252
+ "/root",
1253
+ "/home",
1254
+ "/srv",
1255
+ "/run",
1256
+ "/system",
1257
+ "/library",
1258
+ "/applications",
1259
+ "/users"
1260
+ ]);
1261
+ var CATASTROPHIC_WIN_SUBDIRS = /* @__PURE__ */ new Set([
1262
+ "windows",
1263
+ "system32",
1264
+ "winnt",
1265
+ "program files",
1266
+ "program files (x86)",
1267
+ "programdata",
1268
+ "users"
1269
+ ]);
1238
1270
  var SHELL_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", "|", ";", ">", ">>", "<", "2>", "2>>"]);
1239
1271
  function getInputString(input, key) {
1240
1272
  if (!input || typeof input !== "object") return void 0;
@@ -1244,27 +1276,28 @@ function getInputString(input, key) {
1244
1276
  function pathLooksInsideProject(rawPath, projectRoot) {
1245
1277
  if (!projectRoot) return false;
1246
1278
  if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
1247
- const resolved = path3.resolve(projectRoot, rawPath);
1248
- const relative2 = path3.relative(projectRoot, resolved);
1249
- return !!relative2 && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
1279
+ const resolved = path.resolve(projectRoot, rawPath);
1280
+ const relative2 = path.relative(projectRoot, resolved);
1281
+ return !!relative2 && !relative2.startsWith("..") && !path.isAbsolute(relative2);
1250
1282
  }
1251
1283
  function tokenizeShell(command) {
1252
1284
  return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
1253
1285
  }
1254
- function pathTokenIsOutsideProject(token, projectRoot) {
1255
- if (!token || SHELL_OPERATORS.has(token) || token.startsWith("-")) return false;
1256
- if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
1257
- if (token.includes("*")) return true;
1258
- if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
1259
- if (path3.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
1286
+ function isCatastrophicDeleteTarget(rawTarget) {
1287
+ const t = rawTarget.replace(/^['"]|['"]$/g, "").trim();
1288
+ if (!t) return false;
1289
+ if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*") return true;
1290
+ const s = t.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
1291
+ if (s === "") return true;
1292
+ if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
1293
+ if (/^[A-Za-z]:$/.test(s)) return true;
1294
+ const norm = s.toLowerCase().replace(/\\/g, "/");
1295
+ if (CATASTROPHIC_POSIX_ROOTS.has(norm)) return true;
1296
+ const win = norm.match(/^[a-z]:\/([^/]+)$/);
1297
+ if (win?.[1] && CATASTROPHIC_WIN_SUBDIRS.has(win[1])) return true;
1260
1298
  return false;
1261
1299
  }
1262
- function hasDangerousDeleteTarget(tokens, start, projectRoot) {
1263
- const targets = tokens.slice(start).filter((token) => !token.startsWith("-") && !SHELL_OPERATORS.has(token));
1264
- if (targets.length === 0) return true;
1265
- return targets.some((target) => pathTokenIsOutsideProject(target, projectRoot));
1266
- }
1267
- function hasDestructiveDelete(command, projectRoot) {
1300
+ function hasCatastrophicDelete(command) {
1268
1301
  const tokens = tokenizeShell(command);
1269
1302
  for (let i = 0; i < tokens.length; i++) {
1270
1303
  const token = tokens[i]?.toLowerCase();
@@ -1272,35 +1305,43 @@ function hasDestructiveDelete(command, projectRoot) {
1272
1305
  if (token === "rm") {
1273
1306
  const args = tokens.slice(i + 1);
1274
1307
  const recursiveOrForce = args.some(
1275
- (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force"
1308
+ (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force" || arg === "--no-preserve-root"
1276
1309
  );
1277
- if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1310
+ if (!recursiveOrForce) continue;
1311
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
1312
+ if (targets.length === 0) return true;
1313
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1314
+ }
1315
+ if (token === "remove-item" || token === "ri") {
1316
+ const args = tokens.slice(i + 1);
1317
+ const recursive = args.some((arg) => {
1318
+ const a = arg.toLowerCase();
1319
+ return a === "-recurse" || a === "-force";
1320
+ });
1321
+ if (!recursive) continue;
1322
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
1323
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1278
1324
  }
1279
1325
  if (token === "rmdir" || token === "rd") {
1280
1326
  const args = tokens.slice(i + 1);
1281
1327
  const recursive = args.some((arg) => arg.toLowerCase() === "/s");
1282
- if (recursive && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1328
+ if (!recursive) continue;
1329
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
1330
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1283
1331
  }
1284
1332
  if (token === "del" || token === "erase") {
1285
- if (hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1286
- }
1287
- if (token === "remove-item") {
1288
- const args = tokens.slice(i + 1).map((arg) => arg.toLowerCase());
1289
- const recursiveOrForce = args.includes("-recurse") || args.includes("-force");
1290
- if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1333
+ const args = tokens.slice(i + 1);
1334
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
1335
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1291
1336
  }
1292
1337
  }
1293
1338
  return false;
1294
1339
  }
1295
- function isClearlyDestructiveBashCommand(command, projectRoot) {
1340
+ function isClearlyDestructiveBashCommand(command, _projectRoot) {
1296
1341
  const trimmed = command.trim();
1297
1342
  if (!trimmed) return false;
1298
- if (hasDestructiveDelete(trimmed, projectRoot)) return true;
1299
- if (DESTRUCTIVE_BASH_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
1300
- if (/\bcd\s+(?:\.\.|~|\/|[A-Za-z]:[\\/])/i.test(trimmed)) return true;
1301
- if (PROJECT_ESCAPE_PATTERN.test(trimmed)) return true;
1302
- const absolute = trimmed.match(ABSOLUTE_PATH_PATTERN)?.[0]?.trim().replace(/^['"]|['"]$/g, "");
1303
- if (absolute && !pathLooksInsideProject(absolute, projectRoot)) return true;
1343
+ if (hasCatastrophicDelete(trimmed)) return true;
1344
+ if (CATASTROPHIC_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
1304
1345
  return false;
1305
1346
  }
1306
1347