claude-warden 1.1.10 → 1.2.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/config/warden.default.yaml +11 -0
- package/dist/index.cjs +56 -49
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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"
|
|
@@ -51,6 +51,17 @@ askOnSubshell: true
|
|
|
51
51
|
# - my-sprite
|
|
52
52
|
# - dev-*
|
|
53
53
|
|
|
54
|
+
# Override rules when evaluating commands inside trusted remote contexts
|
|
55
|
+
# (docker exec, kubectl exec, ssh, sprite exec). These overrides are applied
|
|
56
|
+
# as the highest-priority layer only for remote command evaluation.
|
|
57
|
+
# trustedContextOverrides:
|
|
58
|
+
# alwaysAllow:
|
|
59
|
+
# - sudo
|
|
60
|
+
# - apt
|
|
61
|
+
# - apt-get
|
|
62
|
+
# alwaysDeny: []
|
|
63
|
+
# rules: []
|
|
64
|
+
|
|
54
65
|
# Command-specific rules (override built-in rules by command name).
|
|
55
66
|
# The first scope (project > user > default) with a rule for a given command wins.
|
|
56
67
|
# rules:
|
package/dist/index.cjs
CHANGED
|
@@ -18583,7 +18583,7 @@ function evaluateSSHCommand(cmd, config) {
|
|
|
18583
18583
|
};
|
|
18584
18584
|
}
|
|
18585
18585
|
const parsed = parseCommand(remoteCommand);
|
|
18586
|
-
const result = evaluate(parsed, config);
|
|
18586
|
+
const result = evaluate(parsed, configWithContextOverrides(config));
|
|
18587
18587
|
return {
|
|
18588
18588
|
command,
|
|
18589
18589
|
args: args2,
|
|
@@ -18602,6 +18602,35 @@ var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
18602
18602
|
"--workdir",
|
|
18603
18603
|
"--detach-keys"
|
|
18604
18604
|
]);
|
|
18605
|
+
var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
|
|
18606
|
+
function configWithContextOverrides(config) {
|
|
18607
|
+
if (!config.trustedContextOverrides) return config;
|
|
18608
|
+
return {
|
|
18609
|
+
...config,
|
|
18610
|
+
layers: [config.trustedContextOverrides, ...config.layers]
|
|
18611
|
+
};
|
|
18612
|
+
}
|
|
18613
|
+
function evaluateRemoteCommand(remoteArgs, config) {
|
|
18614
|
+
const overriddenConfig = configWithContextOverrides(config);
|
|
18615
|
+
if (remoteArgs.length === 0) {
|
|
18616
|
+
return { decision: "allow", reason: "interactive", details: [] };
|
|
18617
|
+
}
|
|
18618
|
+
const remoteCmd = remoteArgs[0];
|
|
18619
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs.length === 1) {
|
|
18620
|
+
return { decision: "allow", reason: "interactive shell", details: [] };
|
|
18621
|
+
}
|
|
18622
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
18623
|
+
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
18624
|
+
const parsed2 = parseCommand(innerCommand);
|
|
18625
|
+
return evaluate(parsed2, overriddenConfig);
|
|
18626
|
+
}
|
|
18627
|
+
const parsed = {
|
|
18628
|
+
commands: [{ command: remoteCmd, args: remoteArgs.slice(1) }],
|
|
18629
|
+
hasSubshell: false,
|
|
18630
|
+
subshellCommands: []
|
|
18631
|
+
};
|
|
18632
|
+
return evaluate(parsed, overriddenConfig);
|
|
18633
|
+
}
|
|
18605
18634
|
function parseDockerExecArgs(args2) {
|
|
18606
18635
|
let target = null;
|
|
18607
18636
|
const remoteArgs = [];
|
|
@@ -18627,29 +18656,19 @@ function parseDockerExecArgs(args2) {
|
|
|
18627
18656
|
}
|
|
18628
18657
|
i++;
|
|
18629
18658
|
}
|
|
18630
|
-
return { target,
|
|
18659
|
+
return { target, remoteArgs };
|
|
18631
18660
|
}
|
|
18632
18661
|
function evaluateDockerExec(cmd, config) {
|
|
18633
18662
|
const { command, args: args2 } = cmd;
|
|
18634
18663
|
if (args2[0] !== "exec") return null;
|
|
18635
|
-
const { target,
|
|
18664
|
+
const { target, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
18636
18665
|
if (!target || !matchesPattern(target, config.trustedDockerContainers || [])) return null;
|
|
18637
|
-
|
|
18638
|
-
return {
|
|
18639
|
-
command,
|
|
18640
|
-
args: args2,
|
|
18641
|
-
decision: "allow",
|
|
18642
|
-
reason: `Trusted Docker container "${target}" (interactive)`,
|
|
18643
|
-
matchedRule: "trustedDockerContainers"
|
|
18644
|
-
};
|
|
18645
|
-
}
|
|
18646
|
-
const parsed = parseCommand(remoteCommand);
|
|
18647
|
-
const result = evaluate(parsed, config);
|
|
18666
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18648
18667
|
return {
|
|
18649
18668
|
command,
|
|
18650
18669
|
args: args2,
|
|
18651
18670
|
decision: result.decision,
|
|
18652
|
-
reason: `Trusted Docker container "${target}"
|
|
18671
|
+
reason: `Trusted Docker container "${target}" (${result.reason})`,
|
|
18653
18672
|
matchedRule: "trustedDockerContainers"
|
|
18654
18673
|
};
|
|
18655
18674
|
}
|
|
@@ -18684,11 +18703,9 @@ function parseKubectlExecArgs(args2) {
|
|
|
18684
18703
|
let pod = null;
|
|
18685
18704
|
const remoteArgs = [];
|
|
18686
18705
|
let i = 0;
|
|
18687
|
-
let pastSeparator = false;
|
|
18688
18706
|
while (i < args2.length) {
|
|
18689
18707
|
const arg = args2[i];
|
|
18690
18708
|
if (arg === "--") {
|
|
18691
|
-
pastSeparator = true;
|
|
18692
18709
|
i++;
|
|
18693
18710
|
while (i < args2.length) {
|
|
18694
18711
|
remoteArgs.push(args2[i]);
|
|
@@ -18717,30 +18734,20 @@ function parseKubectlExecArgs(args2) {
|
|
|
18717
18734
|
}
|
|
18718
18735
|
i++;
|
|
18719
18736
|
}
|
|
18720
|
-
return { context, pod,
|
|
18737
|
+
return { context, pod, remoteArgs };
|
|
18721
18738
|
}
|
|
18722
18739
|
function evaluateKubectlExec(cmd, config) {
|
|
18723
18740
|
const { command, args: args2 } = cmd;
|
|
18724
18741
|
if (args2[0] !== "exec") return null;
|
|
18725
|
-
const { context, pod,
|
|
18742
|
+
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
18726
18743
|
const trustedContexts = config.trustedKubectlContexts || [];
|
|
18727
18744
|
if (!context || !matchesPattern(context, trustedContexts)) return null;
|
|
18728
|
-
|
|
18729
|
-
return {
|
|
18730
|
-
command,
|
|
18731
|
-
args: args2,
|
|
18732
|
-
decision: "allow",
|
|
18733
|
-
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (interactive)`,
|
|
18734
|
-
matchedRule: "trustedKubectlContexts"
|
|
18735
|
-
};
|
|
18736
|
-
}
|
|
18737
|
-
const parsed = parseCommand(remoteCommand);
|
|
18738
|
-
const result = evaluate(parsed, config);
|
|
18745
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18739
18746
|
return {
|
|
18740
18747
|
command,
|
|
18741
18748
|
args: args2,
|
|
18742
18749
|
decision: result.decision,
|
|
18743
|
-
reason: `Trusted kubectl context "${context}": ${result.reason}`,
|
|
18750
|
+
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (${result.reason})`,
|
|
18744
18751
|
matchedRule: "trustedKubectlContexts"
|
|
18745
18752
|
};
|
|
18746
18753
|
}
|
|
@@ -18785,7 +18792,7 @@ function parseSpriteExecArgs(args2) {
|
|
|
18785
18792
|
i++;
|
|
18786
18793
|
continue;
|
|
18787
18794
|
}
|
|
18788
|
-
return { spriteName: null,
|
|
18795
|
+
return { spriteName: null, remoteArgs: [] };
|
|
18789
18796
|
}
|
|
18790
18797
|
while (i < args2.length) {
|
|
18791
18798
|
remoteArgs.push(args2[i]);
|
|
@@ -18793,32 +18800,19 @@ function parseSpriteExecArgs(args2) {
|
|
|
18793
18800
|
}
|
|
18794
18801
|
break;
|
|
18795
18802
|
}
|
|
18796
|
-
return {
|
|
18797
|
-
spriteName,
|
|
18798
|
-
remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null
|
|
18799
|
-
};
|
|
18803
|
+
return { spriteName, remoteArgs };
|
|
18800
18804
|
}
|
|
18801
18805
|
function evaluateSpriteExec(cmd, config) {
|
|
18802
18806
|
const { command, args: args2 } = cmd;
|
|
18803
|
-
const { spriteName,
|
|
18807
|
+
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
18804
18808
|
const trustedSprites = config.trustedSprites || [];
|
|
18805
18809
|
if (!spriteName || !matchesPattern(spriteName, trustedSprites)) return null;
|
|
18806
|
-
|
|
18807
|
-
return {
|
|
18808
|
-
command,
|
|
18809
|
-
args: args2,
|
|
18810
|
-
decision: "allow",
|
|
18811
|
-
reason: `Trusted sprite "${spriteName}" (interactive)`,
|
|
18812
|
-
matchedRule: "trustedSprites"
|
|
18813
|
-
};
|
|
18814
|
-
}
|
|
18815
|
-
const parsed = parseCommand(remoteCommand);
|
|
18816
|
-
const result = evaluate(parsed, config);
|
|
18810
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18817
18811
|
return {
|
|
18818
18812
|
command,
|
|
18819
18813
|
args: args2,
|
|
18820
18814
|
decision: result.decision,
|
|
18821
|
-
reason: `Trusted sprite "${spriteName}"
|
|
18815
|
+
reason: `Trusted sprite "${spriteName}" (${result.reason})`,
|
|
18822
18816
|
matchedRule: "trustedSprites"
|
|
18823
18817
|
};
|
|
18824
18818
|
}
|
|
@@ -19359,6 +19353,19 @@ function mergeNonLayerFields(config, raw) {
|
|
|
19359
19353
|
if (typeof raw.askOnSubshell === "boolean") {
|
|
19360
19354
|
config.askOnSubshell = raw.askOnSubshell;
|
|
19361
19355
|
}
|
|
19356
|
+
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19357
|
+
const overrides = raw.trustedContextOverrides;
|
|
19358
|
+
const layer = extractLayer(overrides);
|
|
19359
|
+
if (config.trustedContextOverrides) {
|
|
19360
|
+
config.trustedContextOverrides = {
|
|
19361
|
+
alwaysAllow: [...layer.alwaysAllow, ...config.trustedContextOverrides.alwaysAllow],
|
|
19362
|
+
alwaysDeny: [...layer.alwaysDeny, ...config.trustedContextOverrides.alwaysDeny],
|
|
19363
|
+
rules: [...layer.rules, ...config.trustedContextOverrides.rules]
|
|
19364
|
+
};
|
|
19365
|
+
} else {
|
|
19366
|
+
config.trustedContextOverrides = layer;
|
|
19367
|
+
}
|
|
19368
|
+
}
|
|
19362
19369
|
}
|
|
19363
19370
|
|
|
19364
19371
|
// src/suggest.ts
|