claude-warden 1.6.0 → 1.8.0
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/.claude-plugin/plugin.json +1 -1
- package/dist/index.cjs +301 -33
- package/package.json +6 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Smart command safety filter for Claude Code — parses shell pipelines and evaluates per-command safety rules to auto-approve safe commands and block dangerous ones",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "banyudu"
|
package/dist/index.cjs
CHANGED
|
@@ -18385,6 +18385,15 @@ function parseCommand(input) {
|
|
|
18385
18385
|
|
|
18386
18386
|
// src/evaluator.ts
|
|
18387
18387
|
var import_os = require("os");
|
|
18388
|
+
function safeRegexTest(pattern, input) {
|
|
18389
|
+
try {
|
|
18390
|
+
return new RegExp(pattern).test(input);
|
|
18391
|
+
} catch {
|
|
18392
|
+
process.stderr.write(`[warden] Warning: invalid regex pattern: ${pattern}
|
|
18393
|
+
`);
|
|
18394
|
+
return false;
|
|
18395
|
+
}
|
|
18396
|
+
}
|
|
18388
18397
|
function commandMatchesName(cmd, name) {
|
|
18389
18398
|
if (name.startsWith("/")) {
|
|
18390
18399
|
return cmd.originalCommand === name;
|
|
@@ -18394,7 +18403,11 @@ function commandMatchesName(cmd, name) {
|
|
|
18394
18403
|
}
|
|
18395
18404
|
return cmd.command === name;
|
|
18396
18405
|
}
|
|
18397
|
-
|
|
18406
|
+
var MAX_RECURSION_DEPTH = 10;
|
|
18407
|
+
function evaluate(parsed, config, depth = 0) {
|
|
18408
|
+
if (depth > MAX_RECURSION_DEPTH) {
|
|
18409
|
+
return { decision: "ask", reason: "Maximum recursion depth exceeded", details: [] };
|
|
18410
|
+
}
|
|
18398
18411
|
if (parsed.parseError) {
|
|
18399
18412
|
return { decision: "ask", reason: "Could not parse command safely", details: [] };
|
|
18400
18413
|
}
|
|
@@ -18404,7 +18417,7 @@ function evaluate(parsed, config) {
|
|
|
18404
18417
|
if (parsed.hasSubshell && parsed.subshellCommands.length > 0) {
|
|
18405
18418
|
for (const subCmd of parsed.subshellCommands) {
|
|
18406
18419
|
const subParsed = parseCommand(subCmd);
|
|
18407
|
-
const subResult = evaluate(subParsed, config);
|
|
18420
|
+
const subResult = evaluate(subParsed, config, depth + 1);
|
|
18408
18421
|
if (subResult.decision === "deny") {
|
|
18409
18422
|
return { decision: "deny", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
|
|
18410
18423
|
}
|
|
@@ -18417,7 +18430,7 @@ function evaluate(parsed, config) {
|
|
|
18417
18430
|
}
|
|
18418
18431
|
const details = [];
|
|
18419
18432
|
for (const cmd of parsed.commands) {
|
|
18420
|
-
details.push(evaluateCommand(cmd, config));
|
|
18433
|
+
details.push(evaluateCommand(cmd, config, depth));
|
|
18421
18434
|
}
|
|
18422
18435
|
const decisions = details.map((d) => d.decision);
|
|
18423
18436
|
if (decisions.includes("deny")) {
|
|
@@ -18438,7 +18451,7 @@ function evaluate(parsed, config) {
|
|
|
18438
18451
|
}
|
|
18439
18452
|
return { decision: "allow", reason: "All commands are safe", details };
|
|
18440
18453
|
}
|
|
18441
|
-
function evaluateCommand(cmd, config) {
|
|
18454
|
+
function evaluateCommand(cmd, config, depth = 0) {
|
|
18442
18455
|
const { command, args: args2 } = cmd;
|
|
18443
18456
|
for (const layer of config.layers) {
|
|
18444
18457
|
if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
|
|
@@ -18449,28 +18462,52 @@ function evaluateCommand(cmd, config) {
|
|
|
18449
18462
|
}
|
|
18450
18463
|
}
|
|
18451
18464
|
if ((command === "ssh" || command === "scp" || command === "rsync") && config.trustedSSHHosts?.length) {
|
|
18452
|
-
const sshResult = evaluateSSHCommand(cmd, config);
|
|
18465
|
+
const sshResult = evaluateSSHCommand(cmd, config, depth);
|
|
18453
18466
|
if (sshResult) return sshResult;
|
|
18454
18467
|
}
|
|
18455
18468
|
if (command === "docker" && config.trustedDockerContainers?.length) {
|
|
18456
|
-
const dockerResult = evaluateDockerExec(cmd, config);
|
|
18469
|
+
const dockerResult = evaluateDockerExec(cmd, config, depth);
|
|
18457
18470
|
if (dockerResult) return dockerResult;
|
|
18458
18471
|
}
|
|
18459
18472
|
if (command === "kubectl" && config.trustedKubectlContexts?.length) {
|
|
18460
|
-
const kubectlResult = evaluateKubectlExec(cmd, config);
|
|
18473
|
+
const kubectlResult = evaluateKubectlExec(cmd, config, depth);
|
|
18461
18474
|
if (kubectlResult) return kubectlResult;
|
|
18462
18475
|
}
|
|
18463
18476
|
if (command === "sprite" && config.trustedSprites?.length) {
|
|
18464
|
-
const spriteResult = evaluateSpriteExec(cmd, config);
|
|
18477
|
+
const spriteResult = evaluateSpriteExec(cmd, config, depth);
|
|
18465
18478
|
if (spriteResult) return spriteResult;
|
|
18466
18479
|
}
|
|
18480
|
+
if (command === "xargs") {
|
|
18481
|
+
return evaluateXargsCommand(cmd, config, depth);
|
|
18482
|
+
}
|
|
18483
|
+
const mergedRule = collectMergedRule(cmd, config);
|
|
18484
|
+
if (mergedRule) {
|
|
18485
|
+
return evaluateRule(cmd, mergedRule);
|
|
18486
|
+
}
|
|
18487
|
+
return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
|
|
18488
|
+
}
|
|
18489
|
+
function collectMergedRule(cmd, config) {
|
|
18490
|
+
const matchingRules = [];
|
|
18467
18491
|
for (const layer of config.layers) {
|
|
18468
18492
|
const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
|
|
18469
18493
|
if (rule) {
|
|
18470
|
-
|
|
18494
|
+
matchingRules.push(rule);
|
|
18495
|
+
if (rule.override) break;
|
|
18471
18496
|
}
|
|
18472
18497
|
}
|
|
18473
|
-
|
|
18498
|
+
if (matchingRules.length === 0) return null;
|
|
18499
|
+
if (matchingRules.length === 1) return matchingRules[0];
|
|
18500
|
+
const mergedPatterns = [];
|
|
18501
|
+
for (const rule of matchingRules) {
|
|
18502
|
+
if (rule.argPatterns) {
|
|
18503
|
+
mergedPatterns.push(...rule.argPatterns);
|
|
18504
|
+
}
|
|
18505
|
+
}
|
|
18506
|
+
return {
|
|
18507
|
+
command: matchingRules[0].command,
|
|
18508
|
+
default: matchingRules[0].default,
|
|
18509
|
+
argPatterns: mergedPatterns
|
|
18510
|
+
};
|
|
18474
18511
|
}
|
|
18475
18512
|
function evaluateRule(cmd, rule) {
|
|
18476
18513
|
const { command, args: args2 } = cmd;
|
|
@@ -18482,10 +18519,10 @@ function evaluateRule(cmd, rule) {
|
|
|
18482
18519
|
matched = matched && m.noArgs === (args2.length === 0);
|
|
18483
18520
|
}
|
|
18484
18521
|
if (m.argsMatch && matched) {
|
|
18485
|
-
matched = m.argsMatch.some((re) =>
|
|
18522
|
+
matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
|
|
18486
18523
|
}
|
|
18487
18524
|
if (m.anyArgMatches && matched) {
|
|
18488
|
-
matched = args2.some((arg) => m.anyArgMatches.some((re) =>
|
|
18525
|
+
matched = args2.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
|
|
18489
18526
|
}
|
|
18490
18527
|
if (m.argCount && matched) {
|
|
18491
18528
|
if (m.argCount.min !== void 0) matched = matched && args2.length >= m.argCount.min;
|
|
@@ -18510,6 +18547,126 @@ function evaluateRule(cmd, rule) {
|
|
|
18510
18547
|
matchedRule: `${command}:default`
|
|
18511
18548
|
};
|
|
18512
18549
|
}
|
|
18550
|
+
var XARGS_SHORT_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["E", "I", "L", "n", "P", "s", "S", "d", "a"]);
|
|
18551
|
+
var XARGS_SHORT_FLAGS_NO_VALUE = /* @__PURE__ */ new Set(["0", "e", "o", "p", "r", "t", "x"]);
|
|
18552
|
+
var XARGS_LONG_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
18553
|
+
"--eof",
|
|
18554
|
+
"--replace",
|
|
18555
|
+
"--max-lines",
|
|
18556
|
+
"--max-args",
|
|
18557
|
+
"--max-procs",
|
|
18558
|
+
"--max-chars",
|
|
18559
|
+
"--arg-file",
|
|
18560
|
+
"--delimiter"
|
|
18561
|
+
]);
|
|
18562
|
+
var XARGS_LONG_FLAGS_NO_VALUE = /* @__PURE__ */ new Set([
|
|
18563
|
+
"--null",
|
|
18564
|
+
"--exit",
|
|
18565
|
+
"--open-tty",
|
|
18566
|
+
"--interactive",
|
|
18567
|
+
"--no-run-if-empty",
|
|
18568
|
+
"--verbose",
|
|
18569
|
+
"--show-limits"
|
|
18570
|
+
]);
|
|
18571
|
+
function parseXargsSubcommand(args2) {
|
|
18572
|
+
let i = 0;
|
|
18573
|
+
while (i < args2.length) {
|
|
18574
|
+
const arg = args2[i];
|
|
18575
|
+
if (arg === "--") {
|
|
18576
|
+
i++;
|
|
18577
|
+
break;
|
|
18578
|
+
}
|
|
18579
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
18580
|
+
break;
|
|
18581
|
+
}
|
|
18582
|
+
if (arg.startsWith("--")) {
|
|
18583
|
+
const eqIndex = arg.indexOf("=");
|
|
18584
|
+
const longFlag = eqIndex === -1 ? arg : arg.slice(0, eqIndex);
|
|
18585
|
+
if (XARGS_LONG_FLAGS_WITH_VALUE.has(longFlag)) {
|
|
18586
|
+
if (eqIndex !== -1) {
|
|
18587
|
+
i++;
|
|
18588
|
+
continue;
|
|
18589
|
+
}
|
|
18590
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
18591
|
+
i += 2;
|
|
18592
|
+
continue;
|
|
18593
|
+
}
|
|
18594
|
+
if (XARGS_LONG_FLAGS_NO_VALUE.has(longFlag)) {
|
|
18595
|
+
i++;
|
|
18596
|
+
continue;
|
|
18597
|
+
}
|
|
18598
|
+
return { subcommand: null, unresolved: true };
|
|
18599
|
+
}
|
|
18600
|
+
const short = arg[1];
|
|
18601
|
+
if (XARGS_SHORT_FLAGS_WITH_VALUE.has(short)) {
|
|
18602
|
+
if (arg.length > 2) {
|
|
18603
|
+
i++;
|
|
18604
|
+
continue;
|
|
18605
|
+
}
|
|
18606
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
18607
|
+
i += 2;
|
|
18608
|
+
continue;
|
|
18609
|
+
}
|
|
18610
|
+
const grouped = arg.slice(1).split("");
|
|
18611
|
+
const allKnownNoValue = grouped.every((ch) => XARGS_SHORT_FLAGS_NO_VALUE.has(ch));
|
|
18612
|
+
if (allKnownNoValue) {
|
|
18613
|
+
i++;
|
|
18614
|
+
continue;
|
|
18615
|
+
}
|
|
18616
|
+
return { subcommand: null, unresolved: true };
|
|
18617
|
+
}
|
|
18618
|
+
if (i >= args2.length) {
|
|
18619
|
+
return {
|
|
18620
|
+
unresolved: false,
|
|
18621
|
+
subcommand: {
|
|
18622
|
+
command: "echo",
|
|
18623
|
+
originalCommand: "echo",
|
|
18624
|
+
args: [],
|
|
18625
|
+
envPrefixes: [],
|
|
18626
|
+
raw: "echo"
|
|
18627
|
+
}
|
|
18628
|
+
};
|
|
18629
|
+
}
|
|
18630
|
+
const subcommand = args2[i];
|
|
18631
|
+
const subArgs = args2.slice(i + 1);
|
|
18632
|
+
return {
|
|
18633
|
+
unresolved: false,
|
|
18634
|
+
subcommand: {
|
|
18635
|
+
command: subcommand,
|
|
18636
|
+
originalCommand: subcommand,
|
|
18637
|
+
args: subArgs,
|
|
18638
|
+
envPrefixes: [],
|
|
18639
|
+
raw: [subcommand, ...subArgs].join(" ")
|
|
18640
|
+
}
|
|
18641
|
+
};
|
|
18642
|
+
}
|
|
18643
|
+
function evaluateXargsCommand(cmd, config, depth = 0) {
|
|
18644
|
+
const { command, args: args2 } = cmd;
|
|
18645
|
+
const { subcommand, unresolved } = parseXargsSubcommand(args2);
|
|
18646
|
+
if (unresolved || !subcommand) {
|
|
18647
|
+
return {
|
|
18648
|
+
command,
|
|
18649
|
+
args: args2,
|
|
18650
|
+
decision: "ask",
|
|
18651
|
+
reason: "xargs subcommand could not be resolved safely",
|
|
18652
|
+
matchedRule: "xargs:subcommand"
|
|
18653
|
+
};
|
|
18654
|
+
}
|
|
18655
|
+
const parsed = {
|
|
18656
|
+
commands: [subcommand],
|
|
18657
|
+
hasSubshell: false,
|
|
18658
|
+
subshellCommands: [],
|
|
18659
|
+
parseError: false
|
|
18660
|
+
};
|
|
18661
|
+
const result = evaluate(parsed, config, depth + 1);
|
|
18662
|
+
return {
|
|
18663
|
+
command,
|
|
18664
|
+
args: args2,
|
|
18665
|
+
decision: result.decision,
|
|
18666
|
+
reason: `xargs subcommand "${subcommand.command}": ${result.reason}`,
|
|
18667
|
+
matchedRule: "xargs:subcommand"
|
|
18668
|
+
};
|
|
18669
|
+
}
|
|
18513
18670
|
var SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
18514
18671
|
"-b",
|
|
18515
18672
|
"-c",
|
|
@@ -18604,7 +18761,7 @@ function parseSSHArgs(args2) {
|
|
|
18604
18761
|
}
|
|
18605
18762
|
return {
|
|
18606
18763
|
host,
|
|
18607
|
-
remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null
|
|
18764
|
+
remoteCommand: remoteArgs.length > 0 ? remoteArgs.map(shellQuote).join(" ") : null
|
|
18608
18765
|
};
|
|
18609
18766
|
}
|
|
18610
18767
|
function extractHostFromRemotePath(args2) {
|
|
@@ -18614,7 +18771,7 @@ function extractHostFromRemotePath(args2) {
|
|
|
18614
18771
|
}
|
|
18615
18772
|
return null;
|
|
18616
18773
|
}
|
|
18617
|
-
function evaluateSSHCommand(cmd, config) {
|
|
18774
|
+
function evaluateSSHCommand(cmd, config, depth = 0) {
|
|
18618
18775
|
const { command, args: args2 } = cmd;
|
|
18619
18776
|
const trustedHosts = config.trustedSSHHosts || [];
|
|
18620
18777
|
if (command === "scp" || command === "rsync") {
|
|
@@ -18622,6 +18779,24 @@ function evaluateSSHCommand(cmd, config) {
|
|
|
18622
18779
|
if (!host2) return null;
|
|
18623
18780
|
const target2 = findMatchingTarget(host2, trustedHosts);
|
|
18624
18781
|
if (!target2) return null;
|
|
18782
|
+
if (target2.allowAll || !target2.overrides) {
|
|
18783
|
+
return {
|
|
18784
|
+
command,
|
|
18785
|
+
args: args2,
|
|
18786
|
+
decision: "allow",
|
|
18787
|
+
reason: `Trusted SSH host "${host2}"${target2.allowAll ? " (allowAll)" : ""}`,
|
|
18788
|
+
matchedRule: "trustedSSHHosts"
|
|
18789
|
+
};
|
|
18790
|
+
}
|
|
18791
|
+
if (target2.overrides.alwaysDeny.some((name) => name === command)) {
|
|
18792
|
+
return {
|
|
18793
|
+
command,
|
|
18794
|
+
args: args2,
|
|
18795
|
+
decision: "deny",
|
|
18796
|
+
reason: `Trusted SSH host "${host2}": "${command}" blocked by overrides`,
|
|
18797
|
+
matchedRule: "trustedSSHHosts"
|
|
18798
|
+
};
|
|
18799
|
+
}
|
|
18625
18800
|
return {
|
|
18626
18801
|
command,
|
|
18627
18802
|
args: args2,
|
|
@@ -18653,7 +18828,7 @@ function evaluateSSHCommand(cmd, config) {
|
|
|
18653
18828
|
};
|
|
18654
18829
|
}
|
|
18655
18830
|
const parsed = parseCommand(remoteCommand);
|
|
18656
|
-
const result = evaluate(parsed, configWithContextOverrides(config, target));
|
|
18831
|
+
const result = evaluate(parsed, configWithContextOverrides(config, target), depth + 1);
|
|
18657
18832
|
return {
|
|
18658
18833
|
command,
|
|
18659
18834
|
args: args2,
|
|
@@ -18673,6 +18848,12 @@ var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
18673
18848
|
"--detach-keys"
|
|
18674
18849
|
]);
|
|
18675
18850
|
var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
|
|
18851
|
+
function shellQuote(arg) {
|
|
18852
|
+
if (/[\s"'\\$`!#&|;()<>]/.test(arg)) {
|
|
18853
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
18854
|
+
}
|
|
18855
|
+
return arg;
|
|
18856
|
+
}
|
|
18676
18857
|
function configWithContextOverrides(config, target) {
|
|
18677
18858
|
const overrideLayers = [];
|
|
18678
18859
|
if (target?.overrides) overrideLayers.push(target.overrides);
|
|
@@ -18683,7 +18864,7 @@ function configWithContextOverrides(config, target) {
|
|
|
18683
18864
|
layers: [...overrideLayers, ...config.layers]
|
|
18684
18865
|
};
|
|
18685
18866
|
}
|
|
18686
|
-
function evaluateRemoteCommand(remoteArgs, config, target) {
|
|
18867
|
+
function evaluateRemoteCommand(remoteArgs, config, target, depth = 0) {
|
|
18687
18868
|
if (target?.allowAll) {
|
|
18688
18869
|
return { decision: "allow", reason: "allowAll target", details: [] };
|
|
18689
18870
|
}
|
|
@@ -18698,7 +18879,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
|
|
|
18698
18879
|
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
18699
18880
|
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
18700
18881
|
const parsed2 = parseCommand(innerCommand);
|
|
18701
|
-
return evaluate(parsed2, overriddenConfig);
|
|
18882
|
+
return evaluate(parsed2, overriddenConfig, depth + 1);
|
|
18702
18883
|
}
|
|
18703
18884
|
const parsed = {
|
|
18704
18885
|
commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
@@ -18706,7 +18887,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
|
|
|
18706
18887
|
subshellCommands: [],
|
|
18707
18888
|
parseError: false
|
|
18708
18889
|
};
|
|
18709
|
-
return evaluate(parsed, overriddenConfig);
|
|
18890
|
+
return evaluate(parsed, overriddenConfig, depth + 1);
|
|
18710
18891
|
}
|
|
18711
18892
|
function parseDockerExecArgs(args2) {
|
|
18712
18893
|
let target = null;
|
|
@@ -18735,14 +18916,14 @@ function parseDockerExecArgs(args2) {
|
|
|
18735
18916
|
}
|
|
18736
18917
|
return { target, remoteArgs };
|
|
18737
18918
|
}
|
|
18738
|
-
function evaluateDockerExec(cmd, config) {
|
|
18919
|
+
function evaluateDockerExec(cmd, config, depth = 0) {
|
|
18739
18920
|
const { command, args: args2 } = cmd;
|
|
18740
18921
|
if (args2[0] !== "exec") return null;
|
|
18741
18922
|
const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
18742
18923
|
if (!containerName) return null;
|
|
18743
18924
|
const matched = findMatchingTarget(containerName, config.trustedDockerContainers || []);
|
|
18744
18925
|
if (!matched) return null;
|
|
18745
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched);
|
|
18926
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
18746
18927
|
return {
|
|
18747
18928
|
command,
|
|
18748
18929
|
args: args2,
|
|
@@ -18815,14 +18996,14 @@ function parseKubectlExecArgs(args2) {
|
|
|
18815
18996
|
}
|
|
18816
18997
|
return { context, pod, remoteArgs };
|
|
18817
18998
|
}
|
|
18818
|
-
function evaluateKubectlExec(cmd, config) {
|
|
18999
|
+
function evaluateKubectlExec(cmd, config, depth = 0) {
|
|
18819
19000
|
const { command, args: args2 } = cmd;
|
|
18820
19001
|
if (args2[0] !== "exec") return null;
|
|
18821
19002
|
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
18822
19003
|
if (!context) return null;
|
|
18823
19004
|
const matched = findMatchingTarget(context, config.trustedKubectlContexts || []);
|
|
18824
19005
|
if (!matched) return null;
|
|
18825
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched);
|
|
19006
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
18826
19007
|
return {
|
|
18827
19008
|
command,
|
|
18828
19009
|
args: args2,
|
|
@@ -18882,13 +19063,13 @@ function parseSpriteExecArgs(args2) {
|
|
|
18882
19063
|
}
|
|
18883
19064
|
return { spriteName, remoteArgs };
|
|
18884
19065
|
}
|
|
18885
|
-
function evaluateSpriteExec(cmd, config) {
|
|
19066
|
+
function evaluateSpriteExec(cmd, config, depth = 0) {
|
|
18886
19067
|
const { command, args: args2 } = cmd;
|
|
18887
19068
|
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
18888
19069
|
if (!spriteName) return null;
|
|
18889
19070
|
const matched = findMatchingTarget(spriteName, config.trustedSprites || []);
|
|
18890
19071
|
if (!matched) return null;
|
|
18891
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched);
|
|
19072
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
18892
19073
|
return {
|
|
18893
19074
|
command,
|
|
18894
19075
|
args: args2,
|
|
@@ -19064,7 +19245,6 @@ var DEFAULT_CONFIG = {
|
|
|
19064
19245
|
"wc",
|
|
19065
19246
|
"sort",
|
|
19066
19247
|
"uniq",
|
|
19067
|
-
"tee",
|
|
19068
19248
|
"diff",
|
|
19069
19249
|
"comm",
|
|
19070
19250
|
"cut",
|
|
@@ -19088,7 +19268,6 @@ var DEFAULT_CONFIG = {
|
|
|
19088
19268
|
"rg",
|
|
19089
19269
|
"ag",
|
|
19090
19270
|
"ack",
|
|
19091
|
-
"find",
|
|
19092
19271
|
"fd",
|
|
19093
19272
|
"fzf",
|
|
19094
19273
|
"locate",
|
|
@@ -19135,11 +19314,8 @@ var DEFAULT_CONFIG = {
|
|
|
19135
19314
|
"du",
|
|
19136
19315
|
"lsof",
|
|
19137
19316
|
// Text processing
|
|
19138
|
-
"sed",
|
|
19139
|
-
"awk",
|
|
19140
19317
|
"jq",
|
|
19141
19318
|
"yq",
|
|
19142
|
-
"xargs",
|
|
19143
19319
|
"seq",
|
|
19144
19320
|
// Network diagnostics (read-only)
|
|
19145
19321
|
"nslookup",
|
|
@@ -19236,6 +19412,7 @@ var DEFAULT_CONFIG = {
|
|
|
19236
19412
|
"xcode-select",
|
|
19237
19413
|
"xcrun",
|
|
19238
19414
|
"xcodebuild",
|
|
19415
|
+
"networkQuality",
|
|
19239
19416
|
// Misc safe
|
|
19240
19417
|
"cd",
|
|
19241
19418
|
"pushd",
|
|
@@ -19253,7 +19430,6 @@ var DEFAULT_CONFIG = {
|
|
|
19253
19430
|
"shasum",
|
|
19254
19431
|
"cksum",
|
|
19255
19432
|
"base64",
|
|
19256
|
-
"openssl",
|
|
19257
19433
|
"watch",
|
|
19258
19434
|
"timeout",
|
|
19259
19435
|
"nohup",
|
|
@@ -19345,6 +19521,7 @@ var DEFAULT_CONFIG = {
|
|
|
19345
19521
|
// npx / bunx — package runners
|
|
19346
19522
|
pkgRunnerRule("npx"),
|
|
19347
19523
|
pkgRunnerRule("bunx"),
|
|
19524
|
+
pkgRunnerRule("pnpx"),
|
|
19348
19525
|
// npm / pnpm / yarn — package managers
|
|
19349
19526
|
pkgManagerRule("npm", ["ci", "search", "explain", "prefix", "root", "fund", "doctor", "diff", "pkg", "query", "shrinkwrap"]),
|
|
19350
19527
|
pkgManagerRule("pnpm", ["store", "fetch", "doctor", "patch"]),
|
|
@@ -19437,6 +19614,49 @@ var DEFAULT_CONFIG = {
|
|
|
19437
19614
|
VERSION_HELP_FLAGS
|
|
19438
19615
|
]
|
|
19439
19616
|
},
|
|
19617
|
+
// --- Potentially dangerous text/file tools ---
|
|
19618
|
+
{
|
|
19619
|
+
command: "find",
|
|
19620
|
+
default: "allow",
|
|
19621
|
+
argPatterns: [
|
|
19622
|
+
{ match: { anyArgMatches: ["^-exec$", "^-execdir$", "^-delete$", "^-ok$", "^-okdir$"] }, decision: "ask", reason: "find can execute or delete files" }
|
|
19623
|
+
]
|
|
19624
|
+
},
|
|
19625
|
+
{
|
|
19626
|
+
command: "sed",
|
|
19627
|
+
default: "allow",
|
|
19628
|
+
argPatterns: [
|
|
19629
|
+
{ match: { anyArgMatches: ["^-i$", "^-i\\b", "^--in-place"] }, decision: "ask", reason: "In-place file modification" }
|
|
19630
|
+
]
|
|
19631
|
+
},
|
|
19632
|
+
{
|
|
19633
|
+
command: "awk",
|
|
19634
|
+
default: "allow",
|
|
19635
|
+
argPatterns: [
|
|
19636
|
+
{ match: { argsMatch: ["system\\s*\\(", "\\|\\s*getline", "print\\s*>"] }, decision: "ask", reason: "awk can execute commands or write files" }
|
|
19637
|
+
]
|
|
19638
|
+
},
|
|
19639
|
+
{
|
|
19640
|
+
command: "xargs",
|
|
19641
|
+
default: "ask",
|
|
19642
|
+
argPatterns: [
|
|
19643
|
+
{ match: { noArgs: true }, decision: "allow", description: "xargs with no args runs echo (safe)" }
|
|
19644
|
+
]
|
|
19645
|
+
},
|
|
19646
|
+
{
|
|
19647
|
+
command: "tee",
|
|
19648
|
+
default: "allow",
|
|
19649
|
+
argPatterns: [
|
|
19650
|
+
{ match: { anyArgMatches: ["^/(etc|usr|var|sys|proc|boot|root|lib)"] }, decision: "ask", reason: "Writing to system directory" }
|
|
19651
|
+
]
|
|
19652
|
+
},
|
|
19653
|
+
{
|
|
19654
|
+
command: "openssl",
|
|
19655
|
+
default: "allow",
|
|
19656
|
+
argPatterns: [
|
|
19657
|
+
{ match: { anyArgMatches: ["^(enc|rsautl|pkeyutl|smime|cms)$"] }, decision: "ask", reason: "Encryption/signing operations" }
|
|
19658
|
+
]
|
|
19659
|
+
},
|
|
19440
19660
|
// --- File operations ---
|
|
19441
19661
|
{
|
|
19442
19662
|
command: "rm",
|
|
@@ -19555,6 +19775,12 @@ var DEFAULT_CONFIG = {
|
|
|
19555
19775
|
{ command: "codesign", default: "ask", argPatterns: [
|
|
19556
19776
|
{ match: { anyArgMatches: ["^(-vv|--verify|--display|-d)$"] }, decision: "allow", description: "Verify codesign" }
|
|
19557
19777
|
] },
|
|
19778
|
+
{ command: "networksetup", default: "ask", argPatterns: [
|
|
19779
|
+
{ match: { anyArgMatches: ["^-(get|list|show)"] }, decision: "allow", description: "Read-only network configuration queries" }
|
|
19780
|
+
] },
|
|
19781
|
+
{ command: "scutil", default: "ask", argPatterns: [
|
|
19782
|
+
{ match: { anyArgMatches: ["^--get$", "^--dns$", "^--proxy$", "^--nwi$"] }, decision: "allow", description: "Read-only system configuration queries" }
|
|
19783
|
+
] },
|
|
19558
19784
|
{ command: "osascript", default: "ask" },
|
|
19559
19785
|
{ command: "say", default: "ask" },
|
|
19560
19786
|
// --- Process management ---
|
|
@@ -19567,6 +19793,10 @@ var DEFAULT_CONFIG = {
|
|
|
19567
19793
|
};
|
|
19568
19794
|
|
|
19569
19795
|
// src/rules.ts
|
|
19796
|
+
var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
19797
|
+
function isValidDecision(value) {
|
|
19798
|
+
return VALID_DECISIONS.has(value);
|
|
19799
|
+
}
|
|
19570
19800
|
var USER_CONFIG_PATHS = [
|
|
19571
19801
|
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.yaml"),
|
|
19572
19802
|
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.json")
|
|
@@ -19617,15 +19847,36 @@ function tryLoadFile(filePath) {
|
|
|
19617
19847
|
if (parsed && typeof parsed === "object") {
|
|
19618
19848
|
return parsed;
|
|
19619
19849
|
}
|
|
19620
|
-
} catch {
|
|
19850
|
+
} catch (err) {
|
|
19851
|
+
process.stderr.write(`[warden] Warning: failed to parse config ${filePath}: ${err instanceof Error ? err.message : String(err)}
|
|
19852
|
+
`);
|
|
19621
19853
|
}
|
|
19622
19854
|
return null;
|
|
19623
19855
|
}
|
|
19624
19856
|
function extractLayer(raw) {
|
|
19857
|
+
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19858
|
+
for (const rule of rules) {
|
|
19859
|
+
if (rule && typeof rule === "object") {
|
|
19860
|
+
if (rule.default && !isValidDecision(rule.default)) {
|
|
19861
|
+
process.stderr.write(`[warden] Warning: invalid rule default "${rule.default}" for "${rule.command}", using "ask"
|
|
19862
|
+
`);
|
|
19863
|
+
rule.default = "ask";
|
|
19864
|
+
}
|
|
19865
|
+
if (Array.isArray(rule.argPatterns)) {
|
|
19866
|
+
for (const pattern of rule.argPatterns) {
|
|
19867
|
+
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19868
|
+
process.stderr.write(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule.command}", using "ask"
|
|
19869
|
+
`);
|
|
19870
|
+
pattern.decision = "ask";
|
|
19871
|
+
}
|
|
19872
|
+
}
|
|
19873
|
+
}
|
|
19874
|
+
}
|
|
19875
|
+
}
|
|
19625
19876
|
return {
|
|
19626
19877
|
alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
|
|
19627
19878
|
alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
|
|
19628
|
-
rules
|
|
19879
|
+
rules
|
|
19629
19880
|
};
|
|
19630
19881
|
}
|
|
19631
19882
|
function parseTrustedList(raw) {
|
|
@@ -19657,7 +19908,12 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19657
19908
|
config.trustedSprites = [...config.trustedSprites || [], ...parseTrustedList(raw.trustedSprites)];
|
|
19658
19909
|
}
|
|
19659
19910
|
if (typeof raw.defaultDecision === "string") {
|
|
19660
|
-
|
|
19911
|
+
if (isValidDecision(raw.defaultDecision)) {
|
|
19912
|
+
config.defaultDecision = raw.defaultDecision;
|
|
19913
|
+
} else {
|
|
19914
|
+
process.stderr.write(`[warden] Warning: invalid defaultDecision "${raw.defaultDecision}", ignoring
|
|
19915
|
+
`);
|
|
19916
|
+
}
|
|
19661
19917
|
}
|
|
19662
19918
|
if (typeof raw.askOnSubshell === "boolean") {
|
|
19663
19919
|
config.askOnSubshell = raw.askOnSubshell;
|
|
@@ -19813,10 +20069,22 @@ function sendNotification(title, message, config) {
|
|
|
19813
20069
|
}
|
|
19814
20070
|
|
|
19815
20071
|
// src/index.ts
|
|
20072
|
+
var MAX_STDIN_SIZE = 1024 * 1024;
|
|
19816
20073
|
async function main() {
|
|
19817
20074
|
let raw = "";
|
|
19818
20075
|
for await (const chunk of process.stdin) {
|
|
19819
20076
|
raw += chunk;
|
|
20077
|
+
if (raw.length > MAX_STDIN_SIZE) {
|
|
20078
|
+
const output2 = {
|
|
20079
|
+
hookSpecificOutput: {
|
|
20080
|
+
hookEventName: "PreToolUse",
|
|
20081
|
+
permissionDecision: "ask",
|
|
20082
|
+
permissionDecisionReason: "[warden] Input exceeds size limit"
|
|
20083
|
+
}
|
|
20084
|
+
};
|
|
20085
|
+
process.stdout.write(JSON.stringify(output2));
|
|
20086
|
+
process.exit(0);
|
|
20087
|
+
}
|
|
19820
20088
|
}
|
|
19821
20089
|
let input;
|
|
19822
20090
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Smart command safety filter for Claude Code — auto-approves safe commands, blocks dangerous ones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -28,13 +28,15 @@
|
|
|
28
28
|
"README.md",
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"bash-parser": "^0.5.0",
|
|
33
|
+
"yaml": "^2.4.0"
|
|
34
|
+
},
|
|
31
35
|
"devDependencies": {
|
|
32
36
|
"@types/node": "^20.0.0",
|
|
33
|
-
"bash-parser": "^0.5.0",
|
|
34
37
|
"tsup": "^8.0.0",
|
|
35
38
|
"typescript": "^5.4.0",
|
|
36
|
-
"vitest": "^1.6.0"
|
|
37
|
-
"yaml": "^2.4.0"
|
|
39
|
+
"vitest": "^1.6.0"
|
|
38
40
|
},
|
|
39
41
|
"scripts": {
|
|
40
42
|
"build": "tsup",
|