@wrongstack/tools 0.291.0 → 0.291.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/dist/bash-kill-guard.d.ts +10 -1
- package/dist/bash-kill-guard.d.ts.map +1 -1
- package/dist/bash.js +140 -14
- package/dist/bash.js.map +2 -2
- package/dist/builtin.js +464 -90
- package/dist/builtin.js.map +4 -4
- package/dist/exec-kill-guard.d.ts +29 -0
- package/dist/exec-kill-guard.d.ts.map +1 -0
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +670 -10
- package/dist/exec.js.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +490 -113
- package/dist/index.js.map +4 -4
- package/dist/pack.js +464 -90
- package/dist/pack.js.map +4 -4
- package/package.json +3 -3
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
* - Shell -c wrapped: bash -c "kill -9 12345" (any shell path — see P2 #10)
|
|
12
12
|
* - Full path kills: /bin/kill -9 12345
|
|
13
13
|
* - Name-based kills: pkill, killall, pgrep
|
|
14
|
-
* - Windows equivalents: taskkill
|
|
14
|
+
* - Windows equivalents: taskkill, tskill
|
|
15
|
+
* - PowerShell Stop-Process / kill alias: Stop-Process -Name node, kill -Id 12345
|
|
16
|
+
* - WMIC process termination: wmic process where "name='node.exe'" delete
|
|
17
|
+
* - Script-based kill (script is named kill*.sh, kill*.ps1, kill*.bat)
|
|
18
|
+
*
|
|
19
|
+
* Security contract: every "Handles" bullet must map to both a detector
|
|
20
|
+
* AND a block path in isKillRelatedCommand + parseKillCommand + isKillProtected.
|
|
21
|
+
* Script-based kills are blocked conservatively (can't inspect script content).
|
|
15
22
|
*
|
|
16
23
|
* Known bypasses (NOT handled — this is a static regex parser, not a shell):
|
|
17
24
|
* Static analysis of shell strings is inherently defeatable by obfuscation.
|
|
@@ -25,6 +32,8 @@
|
|
|
25
32
|
* - String concatenation / quote-splitting: `ki''ll -9 12345`, `k"i"ll 12345`
|
|
26
33
|
* - Aliases and functions: `alias x=kill; x -9 12345`
|
|
27
34
|
* - eval / source: `eval "ki""ll -9 12345"`
|
|
35
|
+
* - node -e eval: `node -e "process.kill(12345)"` (handled by exec-kill-guard.ts)
|
|
36
|
+
* - Scripts not named kill/terminate/stop*: `runkill.sh`, `/tmp/cleanup.bat`
|
|
28
37
|
*
|
|
29
38
|
* Mitigation: rely on the permission policy (confirm/deny gate) and YOLO
|
|
30
39
|
* destructive detection as the primary controls; this guard is a best-effort
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-kill-guard.d.ts","sourceRoot":"","sources":["../src/bash-kill-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bash-kill-guard.d.ts","sourceRoot":"","sources":["../src/bash-kill-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAqBH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA+ED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAkPpE;AAqBD;;GAEG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA0CzE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAqDxF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E"}
|
package/dist/bash.js
CHANGED
|
@@ -1199,17 +1199,18 @@ function getPersistentProcessRegistry() {
|
|
|
1199
1199
|
|
|
1200
1200
|
// src/bash-kill-guard.ts
|
|
1201
1201
|
var isWin = os3.platform() === "win32";
|
|
1202
|
+
var SCRIPT_KILL_RE = /^(?:\.\\|\.\/)?(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)(?:\s|$)/i;
|
|
1203
|
+
var SCRIPT_KILL_RE_POSIX = /^(?:\.\/)?(?:kill|terminate|stop)\S*\.sh(?:\s|$)/i;
|
|
1204
|
+
var SCRIPT_KILL_FALLBACK_RE = /^\S*(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)\b/i;
|
|
1202
1205
|
function extractKillCommand(command) {
|
|
1203
1206
|
const normalized = command.replace(/\s+/g, " ").trim();
|
|
1204
|
-
const shellCMatch = normalized.match(
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
if (shellCMatch?.[1]) {
|
|
1208
|
-
const inner = shellCMatch[1].trim();
|
|
1207
|
+
const shellCMatch = normalized.match(/^.+?\s+-c\s+(['"])([\s\S]+)\1$/);
|
|
1208
|
+
if (shellCMatch?.[2]) {
|
|
1209
|
+
const inner = shellCMatch[2].trim();
|
|
1209
1210
|
return isKillRelatedCommand(inner) ? inner : null;
|
|
1210
1211
|
}
|
|
1211
1212
|
const shellCUnquoted = normalized.match(
|
|
1212
|
-
|
|
1213
|
+
/^.+?\s+-c\s+(kill(?:\s+-s\s+[a-zA-Z0-9]+|\s+-[a-zA-Z0-9]+)?\s+\d+)$/
|
|
1213
1214
|
);
|
|
1214
1215
|
if (shellCUnquoted?.[1]) {
|
|
1215
1216
|
return shellCUnquoted[1];
|
|
@@ -1221,22 +1222,47 @@ function isKillRelatedCommand(cmd) {
|
|
|
1221
1222
|
if (isWin) {
|
|
1222
1223
|
if (/^taskkill\s/i.test(normalized)) return true;
|
|
1223
1224
|
if (/^tskill\s/i.test(normalized)) return true;
|
|
1225
|
+
if (/^(stop-process|kill|stop)\s/i.test(normalized)) return true;
|
|
1226
|
+
if (/^wmic\s+process\s/i.test(normalized) && /\bdelete\b/i.test(normalized)) return true;
|
|
1227
|
+
if (SCRIPT_KILL_RE.test(normalized)) return true;
|
|
1224
1228
|
return false;
|
|
1225
1229
|
}
|
|
1226
1230
|
if (/^kill(\s|$)/.test(normalized)) return true;
|
|
1227
1231
|
if (/^(pkill|killall|pgrep|skill)\s/.test(normalized)) return true;
|
|
1228
1232
|
if (/^\/proc\/\d+\/(?:kill|fd)/.test(normalized)) return true;
|
|
1233
|
+
if (SCRIPT_KILL_RE_POSIX.test(normalized)) return true;
|
|
1229
1234
|
return false;
|
|
1230
1235
|
}
|
|
1231
1236
|
function parseKillCommand(command) {
|
|
1232
1237
|
const normalized = command.replace(/\s+/g, " ").trim();
|
|
1233
1238
|
if (isWin) {
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1239
|
+
const hasTaskkillForce = /(?:^|\s)\/F(?=\s|$)/i.test(normalized);
|
|
1240
|
+
const isSimpleTaskkill = /^taskkill\s+/i.test(normalized) && !/[|&<>]/.test(normalized);
|
|
1241
|
+
const taskkillPidMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/PID\s+(\d+)(?=\s|$)/i) : null;
|
|
1242
|
+
if (taskkillPidMatch?.[1]) {
|
|
1243
|
+
return {
|
|
1244
|
+
pid: parseInt(taskkillPidMatch[1], 10),
|
|
1245
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
1246
|
+
isGroupKill: false,
|
|
1247
|
+
isAllKill: false,
|
|
1248
|
+
originalCommand: command
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
const taskkillImMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/IM\s+([^\s/]+)(?=\s|$)/i) : null;
|
|
1252
|
+
if (taskkillImMatch?.[1]) {
|
|
1253
|
+
return {
|
|
1254
|
+
name: taskkillImMatch[1],
|
|
1255
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
1256
|
+
isGroupKill: false,
|
|
1257
|
+
isAllKill: false,
|
|
1258
|
+
originalCommand: command
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
const taskkillFiMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/FI\s+"IMAGENAME\s+eq\s+([^"]+)"(?=\s|$)/i) : null;
|
|
1262
|
+
if (taskkillFiMatch?.[1]) {
|
|
1237
1263
|
return {
|
|
1238
|
-
|
|
1239
|
-
signal:
|
|
1264
|
+
name: taskkillFiMatch[1],
|
|
1265
|
+
signal: hasTaskkillForce ? "FORCE" : "TERM",
|
|
1240
1266
|
isGroupKill: false,
|
|
1241
1267
|
isAllKill: false,
|
|
1242
1268
|
originalCommand: command
|
|
@@ -1244,18 +1270,109 @@ function parseKillCommand(command) {
|
|
|
1244
1270
|
}
|
|
1245
1271
|
const tskillMatch = normalized.match(/^tskill\s+(\d+)/i);
|
|
1246
1272
|
if (tskillMatch?.[1]) {
|
|
1247
|
-
const pidStr = tskillMatch[1];
|
|
1248
1273
|
return {
|
|
1249
|
-
pid: parseInt(
|
|
1274
|
+
pid: parseInt(tskillMatch[1], 10),
|
|
1250
1275
|
signal: "TERM",
|
|
1251
1276
|
isGroupKill: false,
|
|
1252
1277
|
isAllKill: false,
|
|
1253
1278
|
originalCommand: command
|
|
1254
1279
|
};
|
|
1255
1280
|
}
|
|
1281
|
+
const isStopProcIdCommand = /^(?:stop-process|kill)(?:\s+-(?:id|pid)\s+\d+|\s+-[a-zA-Z]+)+$/i.test(normalized);
|
|
1282
|
+
const stopProcIdMatch = normalized.match(/(?:^|\s)-(?:id|pid)\s+(\d+)(?=\s|$)/i);
|
|
1283
|
+
if (isStopProcIdCommand && stopProcIdMatch?.[1]) {
|
|
1284
|
+
return {
|
|
1285
|
+
pid: parseInt(stopProcIdMatch[1], 10),
|
|
1286
|
+
signal: "FORCE",
|
|
1287
|
+
isGroupKill: false,
|
|
1288
|
+
isAllKill: false,
|
|
1289
|
+
originalCommand: command
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const killSignalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+)$/i);
|
|
1293
|
+
if (killSignalOptionMatch?.[1] && killSignalOptionMatch[2]) {
|
|
1294
|
+
return {
|
|
1295
|
+
pid: parseInt(killSignalOptionMatch[2], 10),
|
|
1296
|
+
signal: killSignalOptionMatch[1].toUpperCase(),
|
|
1297
|
+
isGroupKill: false,
|
|
1298
|
+
isAllKill: false,
|
|
1299
|
+
originalCommand: command
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
const killPosixMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+)$/);
|
|
1303
|
+
if (killPosixMatch?.[2]) {
|
|
1304
|
+
const sig = killPosixMatch[1] ? killPosixMatch[1].slice(1).toUpperCase() : "TERM";
|
|
1305
|
+
return {
|
|
1306
|
+
pid: parseInt(killPosixMatch[2], 10),
|
|
1307
|
+
signal: sig,
|
|
1308
|
+
isGroupKill: false,
|
|
1309
|
+
isAllKill: false,
|
|
1310
|
+
originalCommand: command
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
const stopProcNameMatch = normalized.match(
|
|
1314
|
+
/^(?:stop-process|kill)\s+-(?:name|n)\s+(?:['"]([a-zA-Z0-9_.-]+)['"]|([a-zA-Z0-9_.-]+))(?:\s|$)/i
|
|
1315
|
+
);
|
|
1316
|
+
const stopProcName = stopProcNameMatch?.[1] ?? stopProcNameMatch?.[2];
|
|
1317
|
+
if (stopProcName) {
|
|
1318
|
+
return {
|
|
1319
|
+
name: stopProcName,
|
|
1320
|
+
signal: "FORCE",
|
|
1321
|
+
isGroupKill: false,
|
|
1322
|
+
isAllKill: false,
|
|
1323
|
+
originalCommand: command
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
const stopProcStandalone = normalized.match(
|
|
1327
|
+
/^(?:stop-process|kill)\s+['"]?([a-zA-Z][a-zA-Z0-9_.-]+)['"]?$/i
|
|
1328
|
+
);
|
|
1329
|
+
if (stopProcStandalone?.[1]) {
|
|
1330
|
+
return {
|
|
1331
|
+
name: stopProcStandalone[1],
|
|
1332
|
+
signal: "FORCE",
|
|
1333
|
+
isGroupKill: false,
|
|
1334
|
+
isAllKill: false,
|
|
1335
|
+
originalCommand: command
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
const wmicMatch = normalized.match(
|
|
1339
|
+
/^wmic\s+process\s+where\s+['"]?(?:name\s*=\s*['"]?)([a-zA-Z0-9_.-]+)/i
|
|
1340
|
+
);
|
|
1341
|
+
if (wmicMatch?.[1]) {
|
|
1342
|
+
return {
|
|
1343
|
+
name: wmicMatch[1],
|
|
1344
|
+
signal: "FORCE",
|
|
1345
|
+
isGroupKill: false,
|
|
1346
|
+
isAllKill: false,
|
|
1347
|
+
originalCommand: command
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const killScriptMatch = normalized.match(SCRIPT_KILL_RE);
|
|
1351
|
+
if (killScriptMatch) {
|
|
1352
|
+
return {
|
|
1353
|
+
name: "kill-script",
|
|
1354
|
+
// sentinel — isKillProtected always blocks "kill-script"
|
|
1355
|
+
signal: "FORCE",
|
|
1356
|
+
isGroupKill: false,
|
|
1357
|
+
isAllKill: false,
|
|
1358
|
+
originalCommand: command
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1256
1361
|
return null;
|
|
1257
1362
|
}
|
|
1258
|
-
const
|
|
1363
|
+
const signalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+|-?\d+)$/i);
|
|
1364
|
+
if (signalOptionMatch?.[1] && signalOptionMatch[2]) {
|
|
1365
|
+
const pidOrGroup = signalOptionMatch[2];
|
|
1366
|
+
const isGroupKill = pidOrGroup.startsWith("-");
|
|
1367
|
+
return {
|
|
1368
|
+
pid: parseInt(isGroupKill ? pidOrGroup.slice(1) : pidOrGroup, 10),
|
|
1369
|
+
signal: signalOptionMatch[1].toUpperCase(),
|
|
1370
|
+
isGroupKill,
|
|
1371
|
+
isAllKill: false,
|
|
1372
|
+
originalCommand: command
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
const simpleMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+|-?\d+)$/);
|
|
1259
1376
|
if (simpleMatch) {
|
|
1260
1377
|
const signal = simpleMatch[1] ?? "-TERM";
|
|
1261
1378
|
const pidOrGroup = simpleMatch[2];
|
|
@@ -1315,6 +1432,9 @@ async function getProtectedEntries() {
|
|
|
1315
1432
|
}
|
|
1316
1433
|
async function isKillProtected(kill) {
|
|
1317
1434
|
const registry = getPersistentProcessRegistry();
|
|
1435
|
+
if (kill.name === "kill-script") {
|
|
1436
|
+
return true;
|
|
1437
|
+
}
|
|
1318
1438
|
if (kill.name) {
|
|
1319
1439
|
const entries = await getProtectedEntries();
|
|
1320
1440
|
const killNameLower = kill.name.toLowerCase();
|
|
@@ -1354,6 +1474,12 @@ async function checkAndBlockKillCommand(command) {
|
|
|
1354
1474
|
reason: `Blocked: complex kill pipeline detected \u2014 "${killCmd.slice(0, 50)}..."`
|
|
1355
1475
|
};
|
|
1356
1476
|
}
|
|
1477
|
+
if (SCRIPT_KILL_FALLBACK_RE.test(killCmd)) {
|
|
1478
|
+
return {
|
|
1479
|
+
blocked: true,
|
|
1480
|
+
reason: `Blocked: script-based kill detected \u2014 "${killCmd.slice(0, 80)}" may target protected WrongStack processes (cannot inspect script body).`
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1357
1483
|
return { blocked: false };
|
|
1358
1484
|
}
|
|
1359
1485
|
if (await isKillProtected(parsed)) {
|