claude-warden 1.1.9 → 1.1.11
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/README.md +29 -0
- package/dist/index.cjs +46 -49
- package/package.json +4 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
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/README.md
CHANGED
|
@@ -168,6 +168,35 @@ All support glob patterns: `*`, `?`, `[...]`, `[!...]`, `{a,b,c}`
|
|
|
168
168
|
5. Results are combined: any deny → deny whole pipeline, any ask → ask, all allow → allow
|
|
169
169
|
6. Returns the decision via stdout JSON (allow/ask) or exit code 2 (deny), with a system message explaining the reasoning for deny/ask decisions
|
|
170
170
|
|
|
171
|
+
## FAQ
|
|
172
|
+
|
|
173
|
+
### Warden says "All commands are safe" but I still get a permission prompt
|
|
174
|
+
|
|
175
|
+
This usually means **another plugin's hook** is overriding Warden's decision. When multiple PreToolUse hooks run, Claude Code uses "most restrictive wins" — if any hook returns `ask`, it overrides another hook's `allow`.
|
|
176
|
+
|
|
177
|
+
**Common culprit:** The `github-dev` plugin ships a `git_commit_confirm.py` hook that returns `permissionDecision: "ask"` for every `git commit` command, regardless of what Warden decides. You'll see something like:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Hook PreToolUse:Bash requires confirmation for this command:
|
|
181
|
+
[warden] All commands are safe
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Warden evaluated the command as safe, but the other hook forced a confirmation prompt.
|
|
185
|
+
|
|
186
|
+
**How to fix:** Uninstall or disable the conflicting plugin. For example:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
/plugin uninstall github-dev
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**How to diagnose:** If you see Warden's `[warden] All commands are safe` message alongside a permission prompt, another hook is the cause. Check your installed plugins for PreToolUse hooks:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
/plugin list
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Then inspect each plugin's `hooks/hooks.json` for PreToolUse entries targeting `Bash`.
|
|
199
|
+
|
|
171
200
|
## Development
|
|
172
201
|
|
|
173
202
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -18602,6 +18602,27 @@ 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 evaluateRemoteCommand(remoteArgs, config) {
|
|
18607
|
+
if (remoteArgs.length === 0) {
|
|
18608
|
+
return { decision: "allow", reason: "interactive", details: [] };
|
|
18609
|
+
}
|
|
18610
|
+
const remoteCmd = remoteArgs[0];
|
|
18611
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs.length === 1) {
|
|
18612
|
+
return { decision: "allow", reason: "interactive shell", details: [] };
|
|
18613
|
+
}
|
|
18614
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
18615
|
+
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
18616
|
+
const parsed2 = parseCommand(innerCommand);
|
|
18617
|
+
return evaluate(parsed2, config);
|
|
18618
|
+
}
|
|
18619
|
+
const parsed = {
|
|
18620
|
+
commands: [{ command: remoteCmd, args: remoteArgs.slice(1) }],
|
|
18621
|
+
hasSubshell: false,
|
|
18622
|
+
subshellCommands: []
|
|
18623
|
+
};
|
|
18624
|
+
return evaluate(parsed, config);
|
|
18625
|
+
}
|
|
18605
18626
|
function parseDockerExecArgs(args2) {
|
|
18606
18627
|
let target = null;
|
|
18607
18628
|
const remoteArgs = [];
|
|
@@ -18627,29 +18648,19 @@ function parseDockerExecArgs(args2) {
|
|
|
18627
18648
|
}
|
|
18628
18649
|
i++;
|
|
18629
18650
|
}
|
|
18630
|
-
return { target,
|
|
18651
|
+
return { target, remoteArgs };
|
|
18631
18652
|
}
|
|
18632
18653
|
function evaluateDockerExec(cmd, config) {
|
|
18633
18654
|
const { command, args: args2 } = cmd;
|
|
18634
18655
|
if (args2[0] !== "exec") return null;
|
|
18635
|
-
const { target,
|
|
18656
|
+
const { target, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
18636
18657
|
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);
|
|
18658
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18648
18659
|
return {
|
|
18649
18660
|
command,
|
|
18650
18661
|
args: args2,
|
|
18651
18662
|
decision: result.decision,
|
|
18652
|
-
reason: `Trusted Docker container "${target}"
|
|
18663
|
+
reason: `Trusted Docker container "${target}" (${result.reason})`,
|
|
18653
18664
|
matchedRule: "trustedDockerContainers"
|
|
18654
18665
|
};
|
|
18655
18666
|
}
|
|
@@ -18684,11 +18695,9 @@ function parseKubectlExecArgs(args2) {
|
|
|
18684
18695
|
let pod = null;
|
|
18685
18696
|
const remoteArgs = [];
|
|
18686
18697
|
let i = 0;
|
|
18687
|
-
let pastSeparator = false;
|
|
18688
18698
|
while (i < args2.length) {
|
|
18689
18699
|
const arg = args2[i];
|
|
18690
18700
|
if (arg === "--") {
|
|
18691
|
-
pastSeparator = true;
|
|
18692
18701
|
i++;
|
|
18693
18702
|
while (i < args2.length) {
|
|
18694
18703
|
remoteArgs.push(args2[i]);
|
|
@@ -18717,30 +18726,20 @@ function parseKubectlExecArgs(args2) {
|
|
|
18717
18726
|
}
|
|
18718
18727
|
i++;
|
|
18719
18728
|
}
|
|
18720
|
-
return { context, pod,
|
|
18729
|
+
return { context, pod, remoteArgs };
|
|
18721
18730
|
}
|
|
18722
18731
|
function evaluateKubectlExec(cmd, config) {
|
|
18723
18732
|
const { command, args: args2 } = cmd;
|
|
18724
18733
|
if (args2[0] !== "exec") return null;
|
|
18725
|
-
const { context, pod,
|
|
18734
|
+
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
18726
18735
|
const trustedContexts = config.trustedKubectlContexts || [];
|
|
18727
18736
|
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);
|
|
18737
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18739
18738
|
return {
|
|
18740
18739
|
command,
|
|
18741
18740
|
args: args2,
|
|
18742
18741
|
decision: result.decision,
|
|
18743
|
-
reason: `Trusted kubectl context "${context}": ${result.reason}`,
|
|
18742
|
+
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (${result.reason})`,
|
|
18744
18743
|
matchedRule: "trustedKubectlContexts"
|
|
18745
18744
|
};
|
|
18746
18745
|
}
|
|
@@ -18785,7 +18784,7 @@ function parseSpriteExecArgs(args2) {
|
|
|
18785
18784
|
i++;
|
|
18786
18785
|
continue;
|
|
18787
18786
|
}
|
|
18788
|
-
return { spriteName: null,
|
|
18787
|
+
return { spriteName: null, remoteArgs: [] };
|
|
18789
18788
|
}
|
|
18790
18789
|
while (i < args2.length) {
|
|
18791
18790
|
remoteArgs.push(args2[i]);
|
|
@@ -18793,32 +18792,19 @@ function parseSpriteExecArgs(args2) {
|
|
|
18793
18792
|
}
|
|
18794
18793
|
break;
|
|
18795
18794
|
}
|
|
18796
|
-
return {
|
|
18797
|
-
spriteName,
|
|
18798
|
-
remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null
|
|
18799
|
-
};
|
|
18795
|
+
return { spriteName, remoteArgs };
|
|
18800
18796
|
}
|
|
18801
18797
|
function evaluateSpriteExec(cmd, config) {
|
|
18802
18798
|
const { command, args: args2 } = cmd;
|
|
18803
|
-
const { spriteName,
|
|
18799
|
+
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
18804
18800
|
const trustedSprites = config.trustedSprites || [];
|
|
18805
18801
|
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);
|
|
18802
|
+
const result = evaluateRemoteCommand(remoteArgs, config);
|
|
18817
18803
|
return {
|
|
18818
18804
|
command,
|
|
18819
18805
|
args: args2,
|
|
18820
18806
|
decision: result.decision,
|
|
18821
|
-
reason: `Trusted sprite "${spriteName}"
|
|
18807
|
+
reason: `Trusted sprite "${spriteName}" (${result.reason})`,
|
|
18822
18808
|
matchedRule: "trustedSprites"
|
|
18823
18809
|
};
|
|
18824
18810
|
}
|
|
@@ -19397,7 +19383,18 @@ function formatSystemMessage(decision, rawCommand, details) {
|
|
|
19397
19383
|
const relevant = details.filter((d) => d.decision !== "allow");
|
|
19398
19384
|
if (decision === "ask") {
|
|
19399
19385
|
const parts = relevant.map((d) => `\`${d.command}\`: ${d.reason}`);
|
|
19400
|
-
|
|
19386
|
+
const header = `[warden] ${parts.join(" | ")}`;
|
|
19387
|
+
const subcommandHints = relevant.filter((d) => d.args.length > 0).map((d) => {
|
|
19388
|
+
const sub = d.args[0];
|
|
19389
|
+
return ` Option A: Allow all \`${d.command}\` \u2192 \`/warden-allow ${d.command}\`
|
|
19390
|
+
Option B: Allow only \`${d.command} ${sub}\` \u2192 \`/warden-allow ${d.command} ${sub}\``;
|
|
19391
|
+
});
|
|
19392
|
+
if (subcommandHints.length > 0) {
|
|
19393
|
+
return `${header}
|
|
19394
|
+
${subcommandHints.join("\n")}
|
|
19395
|
+
See /warden-allow`;
|
|
19396
|
+
}
|
|
19397
|
+
return `${header} \u2014 To auto-allow, see /warden-allow`;
|
|
19401
19398
|
}
|
|
19402
19399
|
const lines = ["[warden] Command blocked", ""];
|
|
19403
19400
|
if (relevant.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-warden",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
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",
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
"typecheck": "tsc --noEmit",
|
|
45
45
|
"eval": "node dist/index.cjs",
|
|
46
46
|
"sync-plugin-version": "node -e \"const p=require('./package.json'),fs=require('fs'),f='.claude-plugin/plugin.json',j=JSON.parse(fs.readFileSync(f));j.version=p.version;fs.writeFileSync(f,JSON.stringify(j,null,2)+'\\n')\"",
|
|
47
|
+
"release": "scripts/release.sh patch",
|
|
48
|
+
"release:minor": "scripts/release.sh minor",
|
|
49
|
+
"release:major": "scripts/release.sh major",
|
|
47
50
|
"docs:dev": "cd docs-src && pnpm dev",
|
|
48
51
|
"docs:build": "cd docs-src && pnpm install && pnpm build"
|
|
49
52
|
}
|