@wrongstack/core 0.277.0 → 0.277.2
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/dist/defaults/index.js +81 -40
- package/dist/defaults/index.js.map +1 -1
- package/dist/index.js +81 -40
- package/dist/index.js.map +1 -1
- package/dist/security/index.js +90 -49
- package/dist/security/index.js.map +1 -1
- package/package.json +1 -1
package/dist/security/index.js
CHANGED
|
@@ -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
|
|
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 =
|
|
195
|
+
const dir = path.dirname(targetPath);
|
|
196
196
|
await fs.mkdir(dir, { recursive: true });
|
|
197
|
-
const 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(
|
|
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(
|
|
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(
|
|
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
|
|
1226
|
-
/\
|
|
1227
|
-
|
|
1228
|
-
/\
|
|
1229
|
-
|
|
1230
|
-
/\
|
|
1231
|
-
|
|
1232
|
-
/\
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
|
1237
|
-
|
|
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 =
|
|
1248
|
-
const relative2 =
|
|
1249
|
-
return !!relative2 && !relative2.startsWith("..") && !
|
|
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
|
|
1255
|
-
|
|
1256
|
-
if (
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1259
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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,
|
|
1340
|
+
function isClearlyDestructiveBashCommand(command, _projectRoot) {
|
|
1296
1341
|
const trimmed = command.trim();
|
|
1297
1342
|
if (!trimmed) return false;
|
|
1298
|
-
if (
|
|
1299
|
-
if (
|
|
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
|
|