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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.1.9",
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, remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null };
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, remoteCommand } = parseDockerExecArgs(args2.slice(1));
18656
+ const { target, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18636
18657
  if (!target || !matchesPattern(target, config.trustedDockerContainers || [])) return null;
18637
- if (!remoteCommand) {
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}": ${result.reason}`,
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, remoteCommand: remoteArgs.length > 0 ? remoteArgs.join(" ") : null };
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, remoteCommand } = parseKubectlExecArgs(args2.slice(1));
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
- if (!remoteCommand) {
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, remoteCommand: 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, remoteCommand } = parseSpriteExecArgs(args2);
18799
+ const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
18804
18800
  const trustedSprites = config.trustedSprites || [];
18805
18801
  if (!spriteName || !matchesPattern(spriteName, trustedSprites)) return null;
18806
- if (!remoteCommand) {
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}": ${result.reason}`,
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
- return `[warden] ${parts.join(" | ")} \u2014 To auto-allow, see /warden-allow`;
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.9",
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
  }