claude-warden 1.5.1 → 1.5.3

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.5.1",
3
+ "version": "1.5.3",
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
@@ -1,5 +1,11 @@
1
1
  # Claude Warden
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/claude-warden)](https://www.npmjs.com/package/claude-warden)
4
+ [![npm downloads](https://img.shields.io/npm/dm/claude-warden)](https://www.npmjs.com/package/claude-warden)
5
+ [![GitHub license](https://img.shields.io/github/license/banyudu/claude-warden)](https://github.com/banyudu/claude-warden/blob/main/LICENSE)
6
+ [![GitHub stars](https://img.shields.io/github/stars/banyudu/claude-warden)](https://github.com/banyudu/claude-warden/stargazers)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/banyudu/claude-warden/ci.yml?label=CI)](https://github.com/banyudu/claude-warden/actions)
8
+
3
9
  Smart command safety filter for [Claude Code](https://claude.ai/code). Parses shell commands, evaluates each against configurable safety rules, and returns allow/deny/ask decisions — eliminating unnecessary permission prompts while blocking dangerous commands.
4
10
 
5
11
  ## The problem
package/dist/index.cjs CHANGED
@@ -18143,9 +18143,57 @@ function preprocessCatHeredocs(input) {
18143
18143
  const regex = /\$\(cat\s+<<-?\s*['"]?(\w+)['"]?\n([\s\S]*?)\n\1\s*\)/g;
18144
18144
  return input.replace(regex, "__HEREDOC_TEXT__");
18145
18145
  }
18146
+ function preprocessPathParentheses(input) {
18147
+ const result = [];
18148
+ let i = 0;
18149
+ while (i < input.length) {
18150
+ const ch = input[i];
18151
+ if (ch === '"' || ch === "'") {
18152
+ const quote = ch;
18153
+ let j = i + 1;
18154
+ while (j < input.length && input[j] !== quote) {
18155
+ if (input[j] === "\\" && quote === '"') j++;
18156
+ j++;
18157
+ }
18158
+ result.push(input.slice(i, j + 1));
18159
+ i = j + 1;
18160
+ continue;
18161
+ }
18162
+ if (ch === "$" && i + 1 < input.length && input[i + 1] === "(") {
18163
+ let depth = 1;
18164
+ let j = i + 2;
18165
+ while (j < input.length && depth > 0) {
18166
+ if (input[j] === "(") depth++;
18167
+ else if (input[j] === ")") depth--;
18168
+ if (depth > 0) j++;
18169
+ }
18170
+ result.push(input.slice(i, j + 1));
18171
+ i = j + 1;
18172
+ continue;
18173
+ }
18174
+ if (ch !== " " && ch !== " " && ch !== "\n") {
18175
+ let j = i;
18176
+ while (j < input.length && !" \n".includes(input[j]) && input[j] !== '"' && input[j] !== "'" && !(input[j] === "$" && j + 1 < input.length && input[j + 1] === "(")) {
18177
+ j++;
18178
+ }
18179
+ const token = input.slice(i, j);
18180
+ if (token.includes("/") && /[()]/.test(token) && !/^[<>|;&]/.test(token)) {
18181
+ result.push('"' + token + '"');
18182
+ } else {
18183
+ result.push(token);
18184
+ }
18185
+ i = j;
18186
+ continue;
18187
+ }
18188
+ result.push(ch);
18189
+ i++;
18190
+ }
18191
+ return result.join("");
18192
+ }
18146
18193
  function convertCommand(node) {
18147
18194
  if (!node.name) return null;
18148
- const command = node.name.text.includes("/") ? (0, import_path.basename)(node.name.text) : node.name.text;
18195
+ const originalCommand = node.name.text;
18196
+ const command = originalCommand.includes("/") ? (0, import_path.basename)(originalCommand) : originalCommand;
18149
18197
  const envPrefixes = [];
18150
18198
  if (node.prefix) {
18151
18199
  for (const p of node.prefix) {
@@ -18168,7 +18216,7 @@ function convertCommand(node) {
18168
18216
  ...args2
18169
18217
  ];
18170
18218
  const raw = rawParts.join(" ");
18171
- return { command, args: args2, envPrefixes, raw };
18219
+ return { command, originalCommand, args: args2, envPrefixes, raw };
18172
18220
  }
18173
18221
  function collectCommandExpansions(node) {
18174
18222
  const commands = [];
@@ -18289,6 +18337,7 @@ function parseCommand(input) {
18289
18337
  return { commands: [], hasSubshell: false, subshellCommands: [], parseError: false };
18290
18338
  }
18291
18339
  input = preprocessCatHeredocs(input);
18340
+ input = preprocessPathParentheses(input);
18292
18341
  try {
18293
18342
  const ast = (0, import_bash_parser.default)(input);
18294
18343
  const result = { commands: [], hasSubshell: false, subshellCommands: [] };
@@ -18335,6 +18384,16 @@ function parseCommand(input) {
18335
18384
  }
18336
18385
 
18337
18386
  // src/evaluator.ts
18387
+ var import_os = require("os");
18388
+ function commandMatchesName(cmd, name) {
18389
+ if (name.startsWith("/")) {
18390
+ return cmd.originalCommand === name;
18391
+ }
18392
+ if (name.startsWith("~/")) {
18393
+ return cmd.originalCommand === (0, import_os.homedir)() + name.slice(1);
18394
+ }
18395
+ return cmd.command === name;
18396
+ }
18338
18397
  function evaluate(parsed, config) {
18339
18398
  if (parsed.parseError) {
18340
18399
  return { decision: "ask", reason: "Could not parse command safely", details: [] };
@@ -18382,10 +18441,10 @@ function evaluate(parsed, config) {
18382
18441
  function evaluateCommand(cmd, config) {
18383
18442
  const { command, args: args2 } = cmd;
18384
18443
  for (const layer of config.layers) {
18385
- if (layer.alwaysDeny.includes(command)) {
18444
+ if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
18386
18445
  return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
18387
18446
  }
18388
- if (layer.alwaysAllow.includes(command)) {
18447
+ if (layer.alwaysAllow.some((name) => commandMatchesName(cmd, name))) {
18389
18448
  return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
18390
18449
  }
18391
18450
  }
@@ -18406,7 +18465,7 @@ function evaluateCommand(cmd, config) {
18406
18465
  if (spriteResult) return spriteResult;
18407
18466
  }
18408
18467
  for (const layer of config.layers) {
18409
- const rule = layer.rules.find((r) => r.command === command);
18468
+ const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
18410
18469
  if (rule) {
18411
18470
  return evaluateRule(cmd, rule);
18412
18471
  }
@@ -18642,7 +18701,7 @@ function evaluateRemoteCommand(remoteArgs, config, target) {
18642
18701
  return evaluate(parsed2, overriddenConfig);
18643
18702
  }
18644
18703
  const parsed = {
18645
- commands: [{ command: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
18704
+ commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
18646
18705
  hasSubshell: false,
18647
18706
  subshellCommands: [],
18648
18707
  parseError: false
@@ -18842,7 +18901,7 @@ function evaluateSpriteExec(cmd, config) {
18842
18901
  // src/rules.ts
18843
18902
  var import_fs = require("fs");
18844
18903
  var import_yaml = __toESM(require_dist2(), 1);
18845
- var import_os = require("os");
18904
+ var import_os2 = require("os");
18846
18905
  var import_path2 = require("path");
18847
18906
 
18848
18907
  // src/defaults.ts
@@ -19486,8 +19545,8 @@ var DEFAULT_CONFIG = {
19486
19545
 
19487
19546
  // src/rules.ts
19488
19547
  var USER_CONFIG_PATHS = [
19489
- (0, import_path2.join)((0, import_os.homedir)(), ".claude", "warden.yaml"),
19490
- (0, import_path2.join)((0, import_os.homedir)(), ".claude", "warden.json")
19548
+ (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.yaml"),
19549
+ (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "warden.json")
19491
19550
  ];
19492
19551
  var PROJECT_CONFIG_NAMES = [
19493
19552
  ".claude/warden.yaml",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
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",