claude-warden 1.2.0 → 1.3.1

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.2.0",
3
+ "version": "1.3.1",
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
@@ -127,14 +127,14 @@ rules:
127
127
  description: Read-only docker commands
128
128
  ```
129
129
 
130
- ## Feedback and `/warden-allow`
130
+ ## Feedback and `/claude-warden:warden-allow`
131
131
 
132
132
  When Warden blocks or flags a command, it includes a system message explaining:
133
133
 
134
134
  1. **Why** the command was blocked/flagged (per-command reasons)
135
135
  2. **How to allow it** — a ready-to-use YAML snippet for your config
136
136
 
137
- Use the `/warden-allow` slash command to apply the suggested config change. It will ask which scope (project or user) to use.
137
+ Use the `/claude-warden:warden-allow` slash command to apply the suggested config change. It will ask which scope (project or user) to use.
138
138
 
139
139
  ## Built-in defaults
140
140
 
@@ -27,29 +27,45 @@ askOnSubshell: true
27
27
  # Trusted SSH hosts — ssh/scp/rsync to these hosts are auto-allowed.
28
28
  # Remote commands on trusted hosts are recursively evaluated through warden rules.
29
29
  # Supports glob patterns (* wildcards).
30
+ # Entries can be strings (use global overrides) or objects with per-target settings.
30
31
  # trustedSSHHosts:
31
- # - devserver
32
+ # - devserver # string → uses global overrides only
32
33
  # - staging-*
33
34
  # - "*.internal.company.com"
34
- # - 192.168.1.*
35
+ # - name: prod-bastion # object with per-target overrides
36
+ # overrides:
37
+ # alwaysAllow: [systemctl]
35
38
 
36
39
  # Trusted Docker containers — docker exec to these containers are auto-allowed.
37
40
  # Remote commands are recursively evaluated through warden rules.
38
41
  # trustedDockerContainers:
39
- # - my-app
40
- # - dev-*
42
+ # - my-app # string → uses global overrides only
43
+ # - name: dev-container # object with allowAll (skip all checks)
44
+ # allowAll: true
45
+ # - name: staging-* # object with per-target overrides
46
+ # overrides:
47
+ # alwaysAllow: [sudo, apt]
41
48
 
42
49
  # Trusted kubectl contexts — kubectl exec in these contexts are auto-allowed.
43
50
  # Remote commands (after --) are recursively evaluated through warden rules.
44
51
  # trustedKubectlContexts:
45
52
  # - minikube
46
- # - dev-cluster-*
53
+ # - name: dev-cluster-*
54
+ # overrides:
55
+ # alwaysAllow: [sudo]
56
+ # - name: prod-cluster
57
+ # overrides:
58
+ # alwaysDeny: [rm]
47
59
 
48
60
  # Trusted Sprites — sprite exec/console to these sprites are auto-allowed.
49
61
  # Remote commands are recursively evaluated through warden rules.
50
62
  # trustedSprites:
51
- # - my-sprite
52
- # - dev-*
63
+ # - my-sprite # string → uses global overrides only
64
+ # - name: yudu-claw # allowAll: skip all checks (disposable env)
65
+ # allowAll: true
66
+ # - name: dev-* # per-target overrides
67
+ # overrides:
68
+ # alwaysAllow: [sudo, apt]
53
69
 
54
70
  # Override rules when evaluating commands inside trusted remote contexts
55
71
  # (docker exec, kubectl exec, ssh, sprite exec). These overrides are applied
package/dist/index.cjs CHANGED
@@ -18515,8 +18515,8 @@ function globToRegex(pattern) {
18515
18515
  }
18516
18516
  return new RegExp(`^${regex}$`);
18517
18517
  }
18518
- function matchesPattern(value, patterns) {
18519
- return patterns.some((p) => globToRegex(p).test(value));
18518
+ function findMatchingTarget(value, targets) {
18519
+ return targets.find((t) => globToRegex(t.name).test(value)) || null;
18520
18520
  }
18521
18521
  function parseSSHArgs(args2) {
18522
18522
  let host = null;
@@ -18560,19 +18560,21 @@ function evaluateSSHCommand(cmd, config) {
18560
18560
  const trustedHosts = config.trustedSSHHosts || [];
18561
18561
  if (command === "scp" || command === "rsync") {
18562
18562
  const host2 = extractHostFromRemotePath(args2);
18563
- if (host2 && matchesPattern(host2, trustedHosts)) {
18564
- return {
18565
- command,
18566
- args: args2,
18567
- decision: "allow",
18568
- reason: `Trusted SSH host "${host2}"`,
18569
- matchedRule: "trustedSSHHosts"
18570
- };
18571
- }
18572
- return null;
18563
+ if (!host2) return null;
18564
+ const target2 = findMatchingTarget(host2, trustedHosts);
18565
+ if (!target2) return null;
18566
+ return {
18567
+ command,
18568
+ args: args2,
18569
+ decision: "allow",
18570
+ reason: `Trusted SSH host "${host2}"`,
18571
+ matchedRule: "trustedSSHHosts"
18572
+ };
18573
18573
  }
18574
18574
  const { host, remoteCommand } = parseSSHArgs(args2);
18575
- if (!host || !matchesPattern(host, trustedHosts)) return null;
18575
+ if (!host) return null;
18576
+ const target = findMatchingTarget(host, trustedHosts);
18577
+ if (!target) return null;
18576
18578
  if (!remoteCommand) {
18577
18579
  return {
18578
18580
  command,
@@ -18582,8 +18584,17 @@ function evaluateSSHCommand(cmd, config) {
18582
18584
  matchedRule: "trustedSSHHosts"
18583
18585
  };
18584
18586
  }
18587
+ if (target.allowAll) {
18588
+ return {
18589
+ command,
18590
+ args: args2,
18591
+ decision: "allow",
18592
+ reason: `Trusted SSH host "${host}" (allowAll)`,
18593
+ matchedRule: "trustedSSHHosts"
18594
+ };
18595
+ }
18585
18596
  const parsed = parseCommand(remoteCommand);
18586
- const result = evaluate(parsed, configWithContextOverrides(config));
18597
+ const result = evaluate(parsed, configWithContextOverrides(config, target));
18587
18598
  return {
18588
18599
  command,
18589
18600
  args: args2,
@@ -18603,15 +18614,21 @@ var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
18603
18614
  "--detach-keys"
18604
18615
  ]);
18605
18616
  var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
18606
- function configWithContextOverrides(config) {
18607
- if (!config.trustedContextOverrides) return config;
18617
+ function configWithContextOverrides(config, target) {
18618
+ const overrideLayers = [];
18619
+ if (target?.overrides) overrideLayers.push(target.overrides);
18620
+ if (config.trustedContextOverrides) overrideLayers.push(config.trustedContextOverrides);
18621
+ if (overrideLayers.length === 0) return config;
18608
18622
  return {
18609
18623
  ...config,
18610
- layers: [config.trustedContextOverrides, ...config.layers]
18624
+ layers: [...overrideLayers, ...config.layers]
18611
18625
  };
18612
18626
  }
18613
- function evaluateRemoteCommand(remoteArgs, config) {
18614
- const overriddenConfig = configWithContextOverrides(config);
18627
+ function evaluateRemoteCommand(remoteArgs, config, target) {
18628
+ if (target?.allowAll) {
18629
+ return { decision: "allow", reason: "allowAll target", details: [] };
18630
+ }
18631
+ const overriddenConfig = configWithContextOverrides(config, target);
18615
18632
  if (remoteArgs.length === 0) {
18616
18633
  return { decision: "allow", reason: "interactive", details: [] };
18617
18634
  }
@@ -18625,9 +18642,10 @@ function evaluateRemoteCommand(remoteArgs, config) {
18625
18642
  return evaluate(parsed2, overriddenConfig);
18626
18643
  }
18627
18644
  const parsed = {
18628
- commands: [{ command: remoteCmd, args: remoteArgs.slice(1) }],
18645
+ commands: [{ command: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
18629
18646
  hasSubshell: false,
18630
- subshellCommands: []
18647
+ subshellCommands: [],
18648
+ parseError: false
18631
18649
  };
18632
18650
  return evaluate(parsed, overriddenConfig);
18633
18651
  }
@@ -18661,14 +18679,16 @@ function parseDockerExecArgs(args2) {
18661
18679
  function evaluateDockerExec(cmd, config) {
18662
18680
  const { command, args: args2 } = cmd;
18663
18681
  if (args2[0] !== "exec") return null;
18664
- const { target, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18665
- if (!target || !matchesPattern(target, config.trustedDockerContainers || [])) return null;
18666
- const result = evaluateRemoteCommand(remoteArgs, config);
18682
+ const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18683
+ if (!containerName) return null;
18684
+ const matched = findMatchingTarget(containerName, config.trustedDockerContainers || []);
18685
+ if (!matched) return null;
18686
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18667
18687
  return {
18668
18688
  command,
18669
18689
  args: args2,
18670
18690
  decision: result.decision,
18671
- reason: `Trusted Docker container "${target}" (${result.reason})`,
18691
+ reason: `Trusted Docker container "${containerName}" (${result.reason})`,
18672
18692
  matchedRule: "trustedDockerContainers"
18673
18693
  };
18674
18694
  }
@@ -18740,9 +18760,10 @@ function evaluateKubectlExec(cmd, config) {
18740
18760
  const { command, args: args2 } = cmd;
18741
18761
  if (args2[0] !== "exec") return null;
18742
18762
  const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
18743
- const trustedContexts = config.trustedKubectlContexts || [];
18744
- if (!context || !matchesPattern(context, trustedContexts)) return null;
18745
- const result = evaluateRemoteCommand(remoteArgs, config);
18763
+ if (!context) return null;
18764
+ const matched = findMatchingTarget(context, config.trustedKubectlContexts || []);
18765
+ if (!matched) return null;
18766
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18746
18767
  return {
18747
18768
  command,
18748
18769
  args: args2,
@@ -18805,9 +18826,10 @@ function parseSpriteExecArgs(args2) {
18805
18826
  function evaluateSpriteExec(cmd, config) {
18806
18827
  const { command, args: args2 } = cmd;
18807
18828
  const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
18808
- const trustedSprites = config.trustedSprites || [];
18809
- if (!spriteName || !matchesPattern(spriteName, trustedSprites)) return null;
18810
- const result = evaluateRemoteCommand(remoteArgs, config);
18829
+ if (!spriteName) return null;
18830
+ const matched = findMatchingTarget(spriteName, config.trustedSprites || []);
18831
+ if (!matched) return null;
18832
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18811
18833
  return {
18812
18834
  command,
18813
18835
  args: args2,
@@ -18904,7 +18926,8 @@ var SAFE_PKG_MANAGER_CMDS = [
18904
18926
  "dedupe",
18905
18927
  "prune",
18906
18928
  "audit",
18907
- "completion"
18929
+ "completion",
18930
+ "whoami"
18908
18931
  ];
18909
18932
  var VERSION_HELP_FLAGS = {
18910
18933
  match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] },
@@ -19057,6 +19080,18 @@ var DEFAULT_CONFIG = {
19057
19080
  "yq",
19058
19081
  "xargs",
19059
19082
  "seq",
19083
+ // Network diagnostics (read-only)
19084
+ "nslookup",
19085
+ "dig",
19086
+ "host",
19087
+ "ping",
19088
+ "traceroute",
19089
+ "mtr",
19090
+ "netstat",
19091
+ "ss",
19092
+ "ifconfig",
19093
+ "ip",
19094
+ "nmap",
19060
19095
  // Pagers and formatters
19061
19096
  "bat",
19062
19097
  "pygmentize",
@@ -19334,18 +19369,33 @@ function extractLayer(raw) {
19334
19369
  rules: Array.isArray(raw.rules) ? raw.rules : []
19335
19370
  };
19336
19371
  }
19372
+ function parseTrustedList(raw) {
19373
+ return raw.map((entry) => {
19374
+ if (typeof entry === "string") return { name: entry };
19375
+ if (entry && typeof entry === "object" && "name" in entry) {
19376
+ const obj = entry;
19377
+ const target = { name: String(obj.name) };
19378
+ if (obj.allowAll === true) target.allowAll = true;
19379
+ if (obj.overrides && typeof obj.overrides === "object") {
19380
+ target.overrides = extractLayer(obj.overrides);
19381
+ }
19382
+ return target;
19383
+ }
19384
+ return null;
19385
+ }).filter((t) => t !== null);
19386
+ }
19337
19387
  function mergeNonLayerFields(config, raw) {
19338
19388
  if (Array.isArray(raw.trustedSSHHosts)) {
19339
- config.trustedSSHHosts = [...config.trustedSSHHosts || [], ...raw.trustedSSHHosts];
19389
+ config.trustedSSHHosts = [...config.trustedSSHHosts || [], ...parseTrustedList(raw.trustedSSHHosts)];
19340
19390
  }
19341
19391
  if (Array.isArray(raw.trustedDockerContainers)) {
19342
- config.trustedDockerContainers = [...config.trustedDockerContainers || [], ...raw.trustedDockerContainers];
19392
+ config.trustedDockerContainers = [...config.trustedDockerContainers || [], ...parseTrustedList(raw.trustedDockerContainers)];
19343
19393
  }
19344
19394
  if (Array.isArray(raw.trustedKubectlContexts)) {
19345
- config.trustedKubectlContexts = [...config.trustedKubectlContexts || [], ...raw.trustedKubectlContexts];
19395
+ config.trustedKubectlContexts = [...config.trustedKubectlContexts || [], ...parseTrustedList(raw.trustedKubectlContexts)];
19346
19396
  }
19347
19397
  if (Array.isArray(raw.trustedSprites)) {
19348
- config.trustedSprites = [...config.trustedSprites || [], ...raw.trustedSprites];
19398
+ config.trustedSprites = [...config.trustedSprites || [], ...parseTrustedList(raw.trustedSprites)];
19349
19399
  }
19350
19400
  if (typeof raw.defaultDecision === "string") {
19351
19401
  config.defaultDecision = raw.defaultDecision;
@@ -19407,15 +19457,15 @@ function formatSystemMessage(decision, rawCommand, details) {
19407
19457
  const header = `[warden] ${parts.join(" | ")}`;
19408
19458
  const subcommandHints = relevant.filter((d) => d.args.length > 0).map((d) => {
19409
19459
  const sub = d.args[0];
19410
- return ` Option A: Allow all \`${d.command}\` \u2192 \`/warden-allow ${d.command}\`
19411
- Option B: Allow only \`${d.command} ${sub}\` \u2192 \`/warden-allow ${d.command} ${sub}\``;
19460
+ return ` Option A: Allow all \`${d.command}\` \u2192 \`/claude-warden:warden-allow ${d.command}\`
19461
+ Option B: Allow only \`${d.command} ${sub}\` \u2192 \`/claude-warden:warden-allow ${d.command} ${sub}\``;
19412
19462
  });
19413
19463
  if (subcommandHints.length > 0) {
19414
19464
  return `${header}
19415
19465
  ${subcommandHints.join("\n")}
19416
- See /warden-allow`;
19466
+ See /claude-warden:warden-allow`;
19417
19467
  }
19418
- return `${header} \u2014 To auto-allow, see /warden-allow`;
19468
+ return `${header} \u2014 To auto-allow, see /claude-warden:warden-allow`;
19419
19469
  }
19420
19470
  const lines = ["[warden] Command blocked", ""];
19421
19471
  if (relevant.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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",