claude-warden 1.7.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 +258 -27
- 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,
|
|
@@ -19231,6 +19412,7 @@ var DEFAULT_CONFIG = {
|
|
|
19231
19412
|
"xcode-select",
|
|
19232
19413
|
"xcrun",
|
|
19233
19414
|
"xcodebuild",
|
|
19415
|
+
"networkQuality",
|
|
19234
19416
|
// Misc safe
|
|
19235
19417
|
"cd",
|
|
19236
19418
|
"pushd",
|
|
@@ -19339,6 +19521,7 @@ var DEFAULT_CONFIG = {
|
|
|
19339
19521
|
// npx / bunx — package runners
|
|
19340
19522
|
pkgRunnerRule("npx"),
|
|
19341
19523
|
pkgRunnerRule("bunx"),
|
|
19524
|
+
pkgRunnerRule("pnpx"),
|
|
19342
19525
|
// npm / pnpm / yarn — package managers
|
|
19343
19526
|
pkgManagerRule("npm", ["ci", "search", "explain", "prefix", "root", "fund", "doctor", "diff", "pkg", "query", "shrinkwrap"]),
|
|
19344
19527
|
pkgManagerRule("pnpm", ["store", "fetch", "doctor", "patch"]),
|
|
@@ -19592,6 +19775,12 @@ var DEFAULT_CONFIG = {
|
|
|
19592
19775
|
{ command: "codesign", default: "ask", argPatterns: [
|
|
19593
19776
|
{ match: { anyArgMatches: ["^(-vv|--verify|--display|-d)$"] }, decision: "allow", description: "Verify codesign" }
|
|
19594
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
|
+
] },
|
|
19595
19784
|
{ command: "osascript", default: "ask" },
|
|
19596
19785
|
{ command: "say", default: "ask" },
|
|
19597
19786
|
// --- Process management ---
|
|
@@ -19604,6 +19793,10 @@ var DEFAULT_CONFIG = {
|
|
|
19604
19793
|
};
|
|
19605
19794
|
|
|
19606
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
|
+
}
|
|
19607
19800
|
var USER_CONFIG_PATHS = [
|
|
19608
19801
|
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.yaml"),
|
|
19609
19802
|
(0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.json")
|
|
@@ -19654,15 +19847,36 @@ function tryLoadFile(filePath) {
|
|
|
19654
19847
|
if (parsed && typeof parsed === "object") {
|
|
19655
19848
|
return parsed;
|
|
19656
19849
|
}
|
|
19657
|
-
} catch {
|
|
19850
|
+
} catch (err) {
|
|
19851
|
+
process.stderr.write(`[warden] Warning: failed to parse config ${filePath}: ${err instanceof Error ? err.message : String(err)}
|
|
19852
|
+
`);
|
|
19658
19853
|
}
|
|
19659
19854
|
return null;
|
|
19660
19855
|
}
|
|
19661
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
|
+
}
|
|
19662
19876
|
return {
|
|
19663
19877
|
alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
|
|
19664
19878
|
alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
|
|
19665
|
-
rules
|
|
19879
|
+
rules
|
|
19666
19880
|
};
|
|
19667
19881
|
}
|
|
19668
19882
|
function parseTrustedList(raw) {
|
|
@@ -19694,7 +19908,12 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19694
19908
|
config.trustedSprites = [...config.trustedSprites || [], ...parseTrustedList(raw.trustedSprites)];
|
|
19695
19909
|
}
|
|
19696
19910
|
if (typeof raw.defaultDecision === "string") {
|
|
19697
|
-
|
|
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
|
+
}
|
|
19698
19917
|
}
|
|
19699
19918
|
if (typeof raw.askOnSubshell === "boolean") {
|
|
19700
19919
|
config.askOnSubshell = raw.askOnSubshell;
|
|
@@ -19850,10 +20069,22 @@ function sendNotification(title, message, config) {
|
|
|
19850
20069
|
}
|
|
19851
20070
|
|
|
19852
20071
|
// src/index.ts
|
|
20072
|
+
var MAX_STDIN_SIZE = 1024 * 1024;
|
|
19853
20073
|
async function main() {
|
|
19854
20074
|
let raw = "";
|
|
19855
20075
|
for await (const chunk of process.stdin) {
|
|
19856
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
|
+
}
|
|
19857
20088
|
}
|
|
19858
20089
|
let input;
|
|
19859
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",
|